SeaQL/sea-orm
 Watch   
 Star   
 Fork   
2026-09-28 00:39:32
sea-orm

sea-orm-cli@2.0.4

Upgrade arrow to 60 (sea-orm-arrow 2.0.0-rc.5)

2026-09-27 23:54:53
sea-orm

2.0.4

SeaORM 2.0.4

(since 2.0.3)

Enhancements

  • Add select_except to select all columns except the given ones https://github.com/SeaQL/sea-orm/pull/3211

    select_except provides the inverse of select_only / column——selecting all columns except the specified ones.

    These methods replace the current selection, so the last one called takes effect.

    #[derive(DerivePartialModel)]
    #[sea_orm(entity = "post::Entity")]
    struct PostPreview {
        id: i32,
        title: String,
    }
    
    let posts = post::Entity::find()
        .select_except([post::Column::Body])
        .into_model::<PostPreview>()
        .all(db)
        .await?;

Bug Fixes

  • Fix chained left_join_linked using the wrong alias for links with multiple relations https://github.com/SeaQL/sea-orm/pull/3199

    When left_join_linked was called more than once, later joins in a multi-relation link could refer to aliases belonging to the first call. The query remained valid but could return incorrect rows.

    cake::Entity::find()
        .left_join_linked(CakeToFilling)
        .left_join_linked(CakeToFillingVendor)
    -- before
    LEFT JOIN `filling` AS `r3` ON `r0`.`filling_id` = `r3`.`id`
    
    -- after
    LEFT JOIN `filling` AS `r3` ON `r2`.`filling_id` = `r3`.`id`
  • Respect condition_type in linked joins https://github.com/SeaQL/sea-orm/pull/3203

    left_join_linked and find_linked always combined the relation condition and on_condition with AND, even when ConditionType::Any was specified.

    They now follow condition_type, matching regular join().

    cake::Relation::Fruit
        .def()
        .condition_type(ConditionType::Any)
        .on_condition(|_left, right| {
            Expr::col((right, fruit::Column::Name))
                .like("%tropical%")
                .into_condition()
        })
    -- before
    LEFT JOIN `fruit` AS `r0` ON `cake`.`id` = `r0`.`cake_id` AND `r0`.`name` LIKE '%tropical%'
    
    -- after
    LEFT JOIN `fruit` AS `r0` ON `cake`.`id` = `r0`.`cake_id` OR `r0`.`name` LIKE '%tropical%'
  • Fix save_as not being applied to array values in eq_any / ne_all https://github.com/SeaQL/sea-orm/pull/3208

    #[sea_orm(column_type = r#"custom("citext")"#, save_as = "citext")]
    pub email: String,
    
    Entity::find().filter(Column::Email.eq_any(["a@x.com".to_owned(), "B@x.com".to_owned()]))
    -- before: text[], case-sensitive
    WHERE "user"."email" = ANY(ARRAY ['a@x.com','B@x.com'])
    
    -- after
    WHERE "user"."email" = ANY(CAST(ARRAY ['a@x.com','B@x.com'] AS citext[]))
  • Fix schema sync failing to drop a stale unique index on MySQL https://github.com/SeaQL/sea-orm/pull/3202

    Removing #[sea_orm(unique)] from a column could make the next schema sync fail with MySQL error 1064.

Upgrades

  • Upgrade arrow to 60 (sea-orm-arrow 2.0.0-rc.5)

    with-arrow now builds against arrow 60. Arrow types passed to SeaORM, such as a RecordBatch for from_arrow, must come from arrow 60 too, so crates like parquet need the matching version.

    arrow = "60"
    parquet = "60"
2026-09-13 18:26:07
sea-orm

sea-orm-cli@2.0.3

update examples

2026-09-13 17:45:35
sea-orm

2.0.3

SeaORM 2.0.3

(since 2.0.2)

Bug Fixes

select_also / select_both with a cast column

Selecting two models panicked when a column needed a cast on select. A combined select derives each A_ / B_ prefixed alias from the selected expression, and a cast is neither a plain column nor an enum cast, so that derivation gave up. Columns now carry their own alias into the combined select and the prefix is applied to it https://github.com/SeaQL/sea-orm/pull/3194

Entity::find().select_also(other::Entity).build(DbBackend::Postgres)
// before: panicked with "cannot apply alias for expr other than Column or AsEnum"
-- after
SELECT "hello"."id" AS "A_id", CAST("hello"."two" AS integer) AS "A_two", ...

Derive macro hygiene

Generated code referred to bindings by plain name, so a field that happened to share a name with one of them shadowed it and the expansion failed to compile. The generated bindings are now hygienic https://github.com/SeaQL/sea-orm/pull/3185

// did not compile before: `row` and `pre` collided with the generated bindings
#[derive(FromQueryResult)]
struct QueryResultProjection {
    row: String,
    pre: String,
}

DeriveActiveEnum with an Error variant

The generated TryFrom<&str> impl wrote its error type as Self::Error, which an Error variant on the enum shadowed. The impl now names sea_orm::DbErr outright https://github.com/SeaQL/sea-orm/pull/3185

#[derive(Debug, EnumIter, DeriveActiveEnum, Eq, PartialEq)]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "error_variant")]
enum ErrorVariantEnum {
    #[sea_orm(string_value = "error")]
    Error, // no longer breaks the generated `TryFrom<&str>`
}

ModelEx and explicitly requested Eq

Eq is deliberately not inherited from the model, because a nested relation may hold non-Eq fields — but the filter also discarded an Eq asked for explicitly. An explicit derive is now kept, and the clippy::derive_partial_eq_without_eq warning that the missing Eq triggered is silenced https://github.com/SeaQL/sea-orm/pull/3193

#[sea_orm::model]
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "user", model_ex_attrs(derive(Eq)))]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
} // ModelEx now derives Eq

PostgreSQL partial unique indexes in entity codegen

A unique index with a WHERE predicate was generated as an unconditional unique column, claiming a constraint the database does not enforce. Partial indexes are now skipped, since codegen cannot express the predicate https://github.com/SeaQL/sea-orm/pull/3196

CREATE UNIQUE INDEX login_human_login_id_key
    ON login (human_login_id)
    WHERE human_login_id IS NOT NULL; -- no longer emits #[sea_orm(unique)]

postgres:db_name connection URLs

The shorthand form has no authority component, so it yields no URL path segments and the CLI reported it as having no database name. The whole path is now read as the database name https://github.com/SeaQL/sea-orm/pull/3197

sea-orm-cli generate entity -u postgres:my_db -o src/entities

Compatibility Notes

  • The alias fix changes emitted SQL for ordinary single-model queries too: a column with a select_as cast is now followed by AS "<column>". Results are unchanged, but tests asserting on generated SQL strings will need updating.

    -- Entity::find(), before
    SELECT "hello"."id", CAST("hello"."two" AS integer), "hello"."three3" FROM "hello"
    
    -- after
    SELECT "hello"."id", CAST("hello"."two" AS integer) AS "two", "hello"."three3" FROM "hello"
  • No public API was added, removed, or changed in sea-orm, sea-orm-migration, sea-orm-codegen, sea-orm-macros or sea-orm-cli.

2026-08-13 05:16:25
sea-orm

sea-orm-cli@2.0.2

Add changelog for 2.0.2

2026-08-13 04:29:59
sea-orm

2.0.2

SeaORM 2.0.2

Enhancements

  • Add require_one to fetch exactly one row, erroring if none: a non-optional counterpart to one() that returns the item directly and yields DbErr::RecordNotFound when no row matches, so call sites can use ? instead of unwrapping an Option. Available on Selector / SelectorRaw and the Select, SelectTwo, and SelectTwoRequired wrappers https://github.com/SeaQL/sea-orm/pull/3164
  • Add date_time_default_now schema helper — a column defaulting to Expr::current_timestamp() https://github.com/SeaQL/sea-orm/pull/3159
  • Add timestamp_default_now and timestamp_with_time_zone_default_now schema helpers, mirroring date_time_default_now for the timestamp family https://github.com/SeaQL/sea-orm/pull/3165

Bug Fixes

  • CLI: deduplicate grouped vs individual imports when regenerating entities with --preserve-user-modifications, so a user-grouped use foo::{A, B} is recognised as equivalent to the freshly generated use foo::A; use foo::B; and no longer emitted twice https://github.com/SeaQL/sea-orm/pull/3163
2026-08-03 04:18:56
sea-orm

sea-orm-cli@2.0.1

Tag seaography example sea-orm version for bump.sh; tolerate taplo padding

2026-08-03 03:49:25
sea-orm

2.0.1

SeaORM 2.0.1

Enhancements

  • Add set_page to Paginator to set the current page https://github.com/SeaQL/sea-orm/pull/2963
  • Add as_option / into_option to ActiveValue<Option<V>>, flattening the outer active-value state and the inner option https://github.com/SeaQL/sea-orm/pull/3155
  • Add set_unset and friends to ActiveValue: set the value only when currently NotSet https://github.com/SeaQL/sea-orm/pull/3083
  • Add is_set_and / is_unchanged_and to ActiveValue https://github.com/SeaQL/sea-orm/pull/3125
  • Add ConnectOptions::test_before_acquire_if_idle_for(Duration) — ping a pooled connection before it is handed out only once it has been idle for at least the given duration, instead of on every acquire; setting it disables test_before_acquire. Also map_sqlx_postgres_before_acquire / map_sqlx_mysql_before_acquire / map_sqlx_sqlite_before_acquire to install a per-backend SQLx before_acquire callback (composes with the idle-ping shorthand: idle-ping first, then the callback), plus the corresponding getters https://github.com/SeaQL/sea-orm/pull/3143
  • Add MigratorTrait::get_pending_migrations_read_only / get_applied_migrations_read_only / get_migration_with_status_read_only (and the with-self equivalents) — query migration status without running CREATE TABLE, so a database user without DDL privileges can check pending migrations; if the migration table does not exist, all migrations are reported as pending https://github.com/SeaQL/sea-orm/pull/3144

Bug Fixes

  • Require TransactionTrait::Transaction to be a fixed point, fixing nested-transaction recursion (E0275 / future_not_send) in #[sea_orm::model] generated save methods https://github.com/SeaQL/sea-orm/pull/3153

    Compatibility note: if you implement TransactionTrait yourself, Self must be Sync, and your Transaction type must be Send and its own transaction type (Transaction::Transaction = Transaction). Implementations delegating to DatabaseConnection / DatabaseTransaction, and virtually all #[async_trait] implementations, already satisfy this. Callers are unaffected.

Upgrades

2026-07-20 08:11:12
sea-orm

sea-orm-cli@2.0.0

Reformat example manifests with taplo after 2.0.0 bump

2026-07-20 06:15:27
sea-orm

2.0.0

SeaORM 2.0.0

SeaORM 2.0 is the first stable release of the 2.x line. It reworks how entities, relations, and ActiveModels are defined and used, adds an entity-first workflow, introduces role-based access control, and brings the library onto SeaQuery 1.0 and SQLx 0.9.

The full, itemized changelog for the 2.0 series (all release candidates included) is in CHANGELOG.md.

Highlights

New entity format

Relations are now declared directly on the Model struct with #[sea_orm::model], replacing the separate Relation enum and Related impls.

#[sea_orm::model]
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
#[sea_orm(table_name = "user")]
pub struct Model {
    #[sea_orm(primary_key)]
    pub id: i32,
    pub name: String,
    #[sea_orm(has_one)]
    pub profile: HasOne<super::profile::Entity>,
    #[sea_orm(has_many)]
    pub posts: HasMany<super::post::Entity>,
}

See the new entity format walk-through.

BelongsTo relation type with compile-time cardinality

A belongs_to relation can be typed BelongsTo<Entity> (required) or BelongsTo<Option<Entity>> (optional), encoding the foreign-key cardinality in the type and paired with the write-side ActiveBelongsTo. The macro validates the type against the nullability of the from columns at compile time. BelongsTo is the recommended type for belongs_to; the legacy HasOne<Entity> field type remains supported for backward compatibility. (https://github.com/SeaQL/sea-orm/pull/3118)

Strongly-typed columns

Filter with the typed COLUMN constant for compile-time type safety, alongside the existing Column enum.

user::Entity::find().filter(user::COLUMN.name.contains("Bob"))

See strongly-typed columns.

Nested ActiveModel

Build and persist an entity together with its related rows in one expression, and push has-many children onto a loaded model.

let bob = user::ActiveModel::builder()
    .set_name("Bob")
    .set_email("bob@sea-ql.org")
    .set_profile(profile::ActiveModel::builder().set_picture("Tennis"))
    .insert(db)
    .await?;

See nested ActiveModel.

Entity Loader

Load an entity together with its relations, including nested relations, in a single call.

let user = user::Entity::load()
    .filter_by_id(12)
    .with(profile::Entity)
    .with((post::Entity, comment::Entity))
    .one(db)
    .await?;

Entity-first workflow

Create tables directly from entity definitions via the schema registry, without writing a migration first.

db.get_schema_registry("my_crate::*").sync(db).await?;

See the entity-first workflow.

Role-Based Access Control

A table-scoped, hierarchical RBAC engine with a query auditor and a RestrictedConnection that implements ConnectionTrait and enforces permissions on all Entity operations (including complex joins, insert-select, and CTE queries). (https://github.com/SeaQL/sea-orm/pull/2683)

Overhauled insert_many

insert_many no longer shares a helper struct with single insert. Panic-prone APIs were removed, empty input returns None / vec![] on exec, and the new InsertMany helper exposes last_insert_id: Option<Value>. (https://github.com/SeaQL/sea-orm/pull/2628)

Synchronous SeaORM

The sea-orm-sync crate provides a synchronous SeaORM backed by rusqlite, mirroring the async API with async/await stripped away.

Upgrading from 1.x

Follow the 1.0 to 2.0 migration guide and the 2.0 walk-through.

Notable breaking changes to be aware of:

  • Expression methods like .eq(), .like(), .contains() now require use sea_orm::ExprTrait; in scope. Also read SeaQuery's breaking changes.
  • execute / query_one / query_all / stream now take a SeaQuery statement; the raw-SQL variants are execute_raw / query_one_raw / query_all_raw / stream_raw.
  • PostgreSQL auto-increment columns now use GENERATED BY DEFAULT AS IDENTITY instead of serial; opt back in with option-postgres-use-serial if needed.
  • SQLite maps both Integer and BigInteger to integer.
  • DeriveValueType now also derives NotU8, IntoActiveValue, and TryFromU64; remove any manual implementations to avoid conflicts.
  • Removed the runtime-actix feature alias (use runtime-tokio); removed DeriveCustomColumn and default_as_str.

Dependencies

  • SeaQuery 1.0
  • SQLx 0.9
  • sea-schema 0.18