SeaQL/sea-orm
 Watch   
 Star   
 Fork   
12 days ago
sea-orm

sea-orm-cli@2.0.0

Reformat example manifests with taplo after 2.0.0 bump

12 days ago
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
17 days ago
sea-orm

2.0.0-rc.43

Release Notes: SeaORM 2.0.0-rc.43

(since 2.0.0-rc.42)

New Features

BelongsTo relation type (#3118, #3133, #3134)

A dedicated BelongsTo<E> / BelongsTo<Option<E>> type for belongs_to relations, alongside the existing HasOne. Cardinality lives in the type parameter, so the foreign-key nullability is expressed — and checked — at compile time:

  • BelongsTo<E> — the FK is NOT NULL; the relation cannot be detached (there is no way to set it to "none" on the active side), so orphaning a required parent is a compile error rather than a runtime constraint violation.
  • BelongsTo<Option<E>> — the FK is nullable; the relation can be detached, which nulls the FK on save.

The cardinality is validated against the FK columns when the entity is derived: BelongsTo<Entity> requires every from column to be NOT NULL, and BelongsTo<Option<Entity>> requires at least one nullable from column — a mismatch is a compile error. Composite foreign keys with mixed nullability detach by nulling only their nullable columns.

#[sea_orm(belongs_to, from = "user_id", to = "id")]
pub author: BelongsTo<super::user::Entity>,          // required

#[sea_orm(belongs_to, from = "bakery_id", to = "id")]
pub bakery: BelongsTo<Option<super::bakery::Entity>>, // optional

The active side is ActiveBelongsTo<..>, mirroring ActiveHasOne / ActiveHasMany.

HasOne still works for belongs_to — no migration required (#3133)

Adopting BelongsTo is opt-in. A belongs_to field may keep its existing HasOne<Entity> type; it continues to compile and behave as before. Use BelongsTo when you want the compile-time cardinality guarantee; otherwise nothing changes.

Enhancements

CLI lists valid options on generation error (#3131)

sea-orm-cli generate entity now prints the valid choices when an invalid option value is supplied, instead of only reporting that the value was rejected.

Bug Fixes

has_related with a Condition::any() filter (#3126)

has_related wrapped the caller's condition and then added the mandatory FK/join condition to it. When the caller passed a Condition::any() (an OR group), the join condition was folded into that disjunction, so the relation constraint was no longer guaranteed. The caller's condition is now wrapped in Condition::all() first, yielding (caller condition) AND (fk join) regardless of any() / all().

Entity codegen for PostgreSQL enum array columns (#3120)

Array-of-enum columns were generated as scalar active-enum fields because the type resolver unwrapped Array(Enum) to Enum. Array columns now route through the full type resolver, so enum[] columns generate Vec<Enum> fields.

Compatibility Notes

  • belongs_to relations may now be typed BelongsTo<Entity> / BelongsTo<Option<Entity>>. This is opt-in — existing HasOne-typed belongs_to fields are unchanged and still supported. sea-orm-cli continues to generate the HasOne form.
  • The compile-time detach guarantee applies only to BelongsTo<Entity> (non-null); it is a property of the type, so it costs nothing at runtime.
  • A BelongsTo field's type parameter must match its FK nullability (checked when the entity is derived). This only affects code that opts into BelongsTo.
  • The nested-ActiveModel relation types remain semver-exempt (unstable): rc.43 drops their PartialEq<Option<..>> impls, so compare an empty relation with is_unloaded_or_not_found() / is_not_found() / as_ref() rather than == None / == Some(..).
27 days ago
sea-orm

2.0.0-rc.42

Release Notes: SeaORM 2.0.0-rc.42

(since 2.0.0-rc.41)

New Features

Typed value arrays via try_getable_array (#3108, #2967)

DeriveValueType wrappers backed by a Vec<_> now round-trip as native PostgreSQL arrays: the derive generates a try_getable_array implementation, so a newtype over Vec<i32> reads and writes as INTEGER[] without a manual TryGetable impl.

#[derive(Clone, Debug, PartialEq, DeriveValueType)]
pub struct Tags(Vec<String>);

Replace and delete a nested HasOne (#3110, #3060, #3061)

The active has-one type (ActiveHasOne) gains a Delete variant plus generated delete_<field> / set_<field>_option builders. Setting a populated has-one now replaces the existing linked record (deleting or orphaning the old one) instead of erroring, and Delete removes it on save.

let mut user = user::Entity::load().filter_by_id(1).with(profile::Entity).one(db).await?.unwrap();
user.delete_profile().save(db).await?; // remove the linked profile

TryFrom<&str> for active enums (#3111)

DeriveActiveEnum now generates a TryFrom<&str> implementation, so a string value can be parsed straight into the enum by its database string representation.

let color = Color::try_from("Black")?;

Enhancements

ActiveHasOne / ActiveHasMany (renamed)

The active/write-side companions to HasOne / HasMany are renamed from HasOneModel / HasManyModel to ActiveHasOne / ActiveHasMany, so the names read as active-model values rather than loaded models. The read-side HasOne / HasMany types are unchanged.

Documented SqlErr and DbErr::sql_err() (#2940)

DbErr::sql_err() and the SqlErr variants are documented, including how to reach the raw driver error for backend-specific handling.

if let Some(SqlErr::UniqueConstraintViolation(_)) = err.sql_err() { /* ... */ }

Schema sync warns on column-type divergence (#3106)

SchemaBuilder::sync() logs a warning when a live column's type diverges from the entity definition instead of silently ignoring the difference.

Bug Fixes

Codegen ColumnType coverage (#3092)

Entity generation emits compiling code for Year, Bit, VarBit, MacAddr, LTree, and Interval columns, and for Money columns carrying precision and scale — previously these produced non-compiling ColumnType expressions.

Skip generated columns in entity generation (#3094)

sea-orm-cli generate entity skips database-generated columns, which cannot be inserted or updated, instead of emitting them as ordinary fields.

SchemaBuilder::sync() returns a Send future (#3100)

Regression fix: sync() no longer returns a non-Send future, so it can be used across .await points on multi-threaded runtimes. Released together with sea-schema 0.18.1.

sea-orm-sync generation fixes (#3112)

The make-sync transform correctly handles futures_util usage in the mock driver, keeping the blocking sea-orm-sync crate in sync with the async source.

Compatibility Notes

  • HasOneModel / HasManyModel are renamed to ActiveHasOne / ActiveHasMany. Update references; the read-side HasOne / HasMany are unaffected.
  • DbErr, UpdateResult, DeleteResult, ActiveHasOne, and ActiveHasMany are now #[non_exhaustive]; downstream matches need a wildcard arm.
  • Schema sync and nested-ActiveModel relation mutation are marked unstable (semver-exempt) while their APIs settle.
  • Prebuilt sea-orm-cli binaries no longer include the Intel macOS (x86_64-apple-darwin) target.
2026-06-19 00:24:21
sea-orm

sea-orm-cli@2.0.0-rc.41

Checkout tags in sea-orm-cli release workflow for --notes-from-tag

2026-06-18 21:45:56
sea-orm

2.0.0-rc.41

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.

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.

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:

#[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.

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.

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.

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

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.

#[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:

#[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:

--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.

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.

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).
2026-04-10 07:06:32
sea-orm

2.0.0-rc.38

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)
2026-03-31 19:39:06
sea-orm

1.1.20

Enhancements

Bug Fixes

2026-03-09 21:52:07
sea-orm

2.0.0-rc.37

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
2026-03-04 19:32:56
sea-orm

2.0.0-rc.36

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