@@ -683,10 +659,8 @@ With this final piece of software, my vision for a complete full-stack Rust envi
## 🖥️ SeaORM Pro: Professional Admin Panel
-
-
-
-
+
+
[SeaORM Pro](https://www.sea-ql.org/sea-orm-pro/) is an admin panel solution allowing you to quickly and easily launch an admin panel for your application - frontend development skills not required, but certainly nice to have!
diff --git a/Blog/blog/2025-06-01-whats-new-in-sea-orm-1.1.md b/Blog/blog/2025-06-01-whats-new-in-sea-orm-1.1.md
new file mode 100644
index 00000000000..89b5737f4c7
--- /dev/null
+++ b/Blog/blog/2025-06-01-whats-new-in-sea-orm-1.1.md
@@ -0,0 +1,525 @@
+---
+slug: 2025-06-01-whats-new-in-sea-orm-1.1
+title: What's new in SeaORM 1.1.12
+author: SeaQL Team
+author_title: Chris Tsang
+author_url: https://github.com/SeaQL
+author_image_url: https://www.sea-ql.org/blog/img/SeaQL.png
+image: https://www.sea-ql.org/blog/img/SeaORM%201.0-rc%20Banner.png
+tags: [news]
+---
+
+
+
+This blog post summarizes the new features and enhancements introduced in SeaORM `1.1`:
+
++ 2025-03-30 [`1.1.8`](https://github.com/SeaQL/sea-orm/releases/tag/1.1.8)
++ 2025-04-14 [`1.1.9`](https://github.com/SeaQL/sea-orm/releases/tag/1.1.9)
++ 2025-04-14 [`1.1.10`](https://github.com/SeaQL/sea-orm/releases/tag/1.1.10)
++ 2025-05-07 [`1.1.11`](https://github.com/SeaQL/sea-orm/releases/tag/1.1.11)
++ 2025-05-27 [`1.1.12`](https://github.com/SeaQL/sea-orm/releases/tag/1.1.12)
+
+## New Features
+
+### Implement `DeriveValueType` for enum strings
+
+`DeriveValueType` now supports `enum` types. It offers a simpler alternative to `DeriveActiveEnum` for client-side enums backed by string database types.
+
+```rust
+#[derive(DeriveValueType)]
+#[sea_orm(value_type = "String")]
+pub enum Tag {
+ Hard,
+ Soft,
+}
+
+// `from_str` defaults to `std::str::FromStr::from_str`
+impl std::str::FromStr for Tag {
+ type Err = sea_orm::sea_query::ValueTypeErr;
+ fn from_str(s: &str) -> Result { .. }
+}
+
+// `to_str` defaults to `std::string::ToString::to_string`.
+impl std::fmt::Display for Tag {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
+}
+```
+
+The following trait impl are generated, removing the boilerplate previously needed:
+
+
+ DeriveValueType expansion
+
+```rust
+#[automatically_derived]
+impl std::convert::From for sea_orm::Value {
+ fn from(source: Tag) -> Self {
+ std::string::ToString::to_string(&source).into()
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::TryGetable for Tag {
+ fn try_get_by(res: &sea_orm::QueryResult, idx: I)
+ -> std::result::Result {
+ let string = String::try_get_by(res, idx)?;
+ std::str::FromStr::from_str(&string).map_err(|err| sea_orm::TryGetError::DbErr(sea_orm::DbErr::Type(format!("{err:?}"))))
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::sea_query::ValueType for Tag {
+ fn try_from(v: sea_orm::Value) -> std::result::Result {
+ let string = ::try_from(v)?;
+ std::str::FromStr::from_str(&string).map_err(|_| sea_orm::sea_query::ValueTypeErr)
+ }
+
+ fn type_name() -> std::string::String {
+ stringify!(Tag).to_owned()
+ }
+
+ fn array_type() -> sea_orm::sea_query::ArrayType {
+ sea_orm::sea_query::ArrayType::String
+ }
+
+ fn column_type() -> sea_orm::sea_query::ColumnType {
+ sea_orm::sea_query::ColumnType::String(sea_orm::sea_query::StringLen::None)
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::sea_query::Nullable for Tag {
+ fn null() -> sea_orm::Value {
+ sea_orm::Value::String(None)
+ }
+}
+```
+
+
+You can override from_str and to_str with custom functions, which is especially useful if you're using [`strum::Display`](https://docs.rs/strum/latest/strum/derive.Display.html) and [`strum::EnumString`](https://docs.rs/strum/latest/strum/derive.EnumString.html), or manually implemented methods:
+
+```rust
+#[derive(DeriveValueType)]
+#[sea_orm(
+ value_type = "String",
+ from_str = "Tag::from_str",
+ to_str = "Tag::to_str"
+)]
+pub enum Tag {
+ Color,
+ Grey,
+}
+
+impl Tag {
+ fn from_str(s: &str) -> Result { .. }
+
+ fn to_str(&self) -> &'static str { .. }
+}
+```
+
+### Support Postgres IpNetwork
+
+[#2395](https://github.com/SeaQL/sea-orm/pull/2395) (under feature flag `with-ipnetwork`)
+
+```rust
+// Model
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "host_network")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub ipaddress: IpNetwork,
+ #[sea_orm(column_type = "Cidr")]
+ pub network: IpNetwork,
+}
+
+// Schema
+sea_query::Table::create()
+ .table(host_network::Entity)
+ .col(ColumnDef::new(host_network::Column::Id).integer().not_null().auto_increment().primary_key())
+ .col(ColumnDef::new(host_network::Column::Ipaddress).inet().not_null())
+ .col(ColumnDef::new(host_network::Column::Network).cidr().not_null())
+ .to_owned();
+
+// CRUD
+host_network::ActiveModel {
+ ipaddress: Set(IpNetwork::new(Ipv6Addr::new(..))),
+ network: Set(IpNetwork::new(Ipv4Addr::new(..))),
+ ..Default::default()
+}
+```
+
+### Added `default_values` to `ActiveModelTrait`
+
+The `ActiveModel::default()` returns `ActiveModel { .. NotSet }` by default (it can also be overridden).
+
+We've added a new method `default_values()` which would set all fields to their actual `Default::default()` values.
+
+This fills in a gap in the type system to help with serde. A real-world use case is to improve `ActiveModel::from_json`, an upcoming [new feature](https://github.com/SeaQL/sea-orm/pull/2599) (which is a breaking change, sadly).
+
+```rust
+#[derive(DeriveEntityModel)]
+#[sea_orm(table_name = "fruit")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ pub cake_id: Option,
+ pub type_without_default: active_enums::Tea,
+}
+
+assert_eq!(
+ fruit::ActiveModel::default_values(),
+ fruit::ActiveModel {
+ id: Set(0), // integer
+ name: Set("".into()), // string
+ cake_id: Set(None), // option
+ type_without_default: NotSet, // not available
+ },
+);
+```
+
+If you are interested in how this works under the hood, a new method [`Value::dummy_value`](https://docs.rs/sea-query/latest/sea_query/value/enum.Value.html#method.dummy_value) is added in SeaQuery:
+
+```rust
+use sea_orm::sea_query::Value;
+let v = Value::Int(None);
+let n = v.dummy_value();
+assert_eq!(n, Value::Int(Some(0)));
+```
+
+The real magic happens with a set of new traits, [`DefaultActiveValue`](https://docs.rs/sea-orm/latest/sea_orm/value/trait.DefaultActiveValue.html), [`DefaultActiveValueNone`](https://docs.rs/sea-orm/latest/sea_orm/value/trait.DefaultActiveValueNone.html) and [`DefaultActiveValueNotSet`](https://docs.rs/sea-orm/latest/sea_orm/value/trait.DefaultActiveValueNotSet.html), and taking advantage of Rust's autoref specialization mechanism used by [anyhow](https://github.com/dtolnay/case-studies/blob/master/autoref-specialization/README.md):
+
+```rust
+use sea_orm::value::{DefaultActiveValue, DefaultActiveValueNone, DefaultActiveValueNotSet};
+
+let v = (&ActiveValue::::NotSet).default_value();
+assert_eq!(v, ActiveValue::Set(0));
+
+let v = (&ActiveValue::
-## Using ActiveEnum on Model
+## Using ActiveEnum in Model
```rust
use sea_orm::entity::prelude::*;
diff --git a/SeaORM/docs/04-generate-entity/05-newtype.md b/SeaORM/docs/04-generate-entity/05-newtype.md
index 81d7cf81c88..9e5d8dcba67 100644
--- a/SeaORM/docs/04-generate-entity/05-newtype.md
+++ b/SeaORM/docs/04-generate-entity/05-newtype.md
@@ -5,7 +5,11 @@ You can define a New Type (`T`) and use it as model field. The following traits
1. Implement `From` for [`sea_query::Value`](https://docs.rs/sea-query/*/sea_query/value/enum.Value.html)
2. Implement [`sea_orm::TryGetable`](https://docs.rs/sea-orm/*/sea_orm/trait.TryGetable.html) for `T`
3. Implement [`sea_query::ValueType`](https://docs.rs/sea-query/*/sea_query/value/trait.ValueType.html) for `T`
-4. If the field is `Option`, implement [`sea_query::Nullable`](https://docs.rs/sea-query/*/sea_query/value/trait.Nullable.html) for `T`
+4. Implement [`sea_query::Nullable`](https://docs.rs/sea-query/*/sea_query/value/trait.Nullable.html) for `T`
+
+## Wrapping scalar types
+
+You can create new types wrapping any type supported by SeaORM.
```rust
use sea_orm::entity::prelude::*;
@@ -16,17 +20,87 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub number: Integer,
- // Postgres only
- pub str_vec: StringVec,
}
-#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
-pub enum Relation {}
-
-impl ActiveModelBehavior for ActiveModel {}
-
#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct Integer(i32);
+```
+
+
+ Which `Integer` expands to:
+
+```rust
+#[automatically_derived]
+impl std::convert::From for sea_orm::Value {
+ fn from(source: Integer) -> Self {
+ source.0.into()
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::TryGetable for Integer {
+ fn try_get_by(res: &sea_orm::QueryResult, idx: I)
+ -> std::result::Result {
+ ::try_get_by(res, idx).map(|v| Integer(v))
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::sea_query::ValueType for Integer {
+ fn try_from(v: sea_orm::Value) -> std::result::Result {
+ ::try_from(v).map(|v| Integer(v))
+ }
+
+ fn type_name() -> std::string::String {
+ stringify!(Integer).to_owned()
+ }
+
+ fn array_type() -> sea_orm::sea_query::ArrayType {
+ sea_orm::sea_query::ArrayType::Int
+ }
+
+ fn column_type() -> sea_orm::sea_query::ColumnType {
+ sea_orm::sea_query::ColumnType::Integer
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::sea_query::Nullable for Integer {
+ fn null() -> sea_orm::Value {
+ ::null()
+ }
+}
+```
+
+
+### Using wrapped types as primary keys
+
+:::tip Since `2.0.0`
+:::
+
+```rust
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "custom_value_type")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: Integer,
+}
+```
+
+Only for `i8` / `i16` / `i32` / `i64` / `u8` / `u16` / `u32` / `u64`.
+
+## Wrapping `Vec` (Postgres only)
+
+```rust
+use sea_orm::entity::prelude::*;
+
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "custom_vec_type")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub str_vec: StringVec,
+}
#[derive(Clone, Debug, PartialEq, Eq, DeriveValueType)]
pub struct StringVec(pub Vec);
@@ -36,18 +110,21 @@ pub struct StringVec(pub Vec);
Which `StringVec` expands to:
```rust
+#[automatically_derived]
impl std::convert::From for Value {
fn from(source: StringVec) -> Self {
source.0.into()
}
}
+#[automatically_derived]
impl sea_orm::TryGetable for StringVec {
fn try_get_by(res: &QueryResult, idx: I) -> Result {
as sea_orm::TryGetable>::try_get_by(res, idx).map(|v| StringVec(v))
}
}
+#[automatically_derived]
impl sea_orm::sea_query::ValueType for StringVec {
fn try_from(v: Value) -> Result {
as sea_orm::sea_query::ValueType>::try_from(v).map(|v| StringVec(v))
@@ -62,24 +139,33 @@ impl sea_orm::sea_query::ValueType for StringVec {
}
fn column_type() -> sea_orm::sea_query::ColumnType {
- sea_orm::sea_query::ColumnType::String(None)
+ sea_orm::sea_query::ColumnType::String(StringLen::None)
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::sea_query::Nullable for Integer {
+ fn null() -> sea_orm::Value {
+ as sea_orm::sea_query::Nullable>::null()
}
}
```
-You can also define a backend-generic `Vec` field by serialize / deserialize the object to / from JSON:
+## Wrapping `Vec` (backend generic)
+
+You can also wrap a `Vec` field by serialize / deserialize the object to / from JSON. This is a backend-generic way of supporting array types across databases.
```rust
use sea_orm::entity::prelude::*;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "json_vec")]
+#[sea_orm(table_name = "json_vec_type")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
- pub str_vec: ObjectVec,
+ pub json_vec: ObjectVec,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, FromJsonQueryResult)]
@@ -132,4 +218,154 @@ impl sea_orm::sea_query::Nullable for ObjectVec {
}
}
```
-
\ No newline at end of file
+
+
+## Treat any type as JSON
+
+In addition to wrapping `Vec`, the `FromJsonQueryResult` macro can be used on any type that implements `serde`'s `Serialize` and `Deserialize`, and they will be converted to/from JSON when interacting with databases.
+
+```rust
+use sea_orm::FromJsonQueryResult;
+use sea_orm::entity::prelude::*;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
+#[sea_orm(table_name = "json_struct")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub json_value: Metadata,
+ pub json_value_opt: Option,
+}
+
+#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromJsonQueryResult)]
+pub struct Metadata {
+ pub id: i32,
+ pub name: String,
+ pub price: f32,
+ pub notes: Option,
+}
+```
+
+## Enum String
+
+Since `1.1.8`, `DeriveValueType` also supports `enum` types. It offers a simpler alternative to `DeriveActiveEnum` for client-side enums backed by string database types.
+
+```rust
+#[derive(DeriveValueType)]
+#[sea_orm(value_type = "String")]
+pub enum Tag {
+ Hard,
+ Soft,
+}
+
+// `from_str` defaults to `std::str::FromStr::from_str`
+impl std::str::FromStr for Tag {
+ type Err = sea_orm::sea_query::ValueTypeErr;
+ fn from_str(s: &str) -> Result { .. }
+}
+
+// `to_str` defaults to `std::string::ToString::to_string`.
+impl std::fmt::Display for Tag {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { .. }
+}
+```
+
+
+ Which `Tag` expands to:
+
+```rust
+#[automatically_derived]
+impl std::convert::From for sea_orm::Value {
+ fn from(source: Tag) -> Self {
+ std::string::ToString::to_string(&source).into()
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::TryGetable for Tag {
+ fn try_get_by(res: &sea_orm::QueryResult, idx: I)
+ -> std::result::Result {
+ let string = String::try_get_by(res, idx)?;
+ std::str::FromStr::from_str(&string).map_err(|err| {
+ sea_orm::TryGetError::DbErr(sea_orm::DbErr::TryIntoErr {
+ from: "String",
+ into: stringify!(#name),
+ source: std::sync::Arc::new(err),
+ })
+ })
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::sea_query::ValueType for Tag {
+ fn try_from(v: sea_orm::Value) -> std::result::Result {
+ let string = ::try_from(v)?;
+ std::str::FromStr::from_str(&string).map_err(|_| sea_orm::sea_query::ValueTypeErr)
+ }
+
+ fn type_name() -> std::string::String {
+ stringify!(Tag).to_owned()
+ }
+
+ fn array_type() -> sea_orm::sea_query::ArrayType {
+ sea_orm::sea_query::ArrayType::String
+ }
+
+ fn column_type() -> sea_orm::sea_query::ColumnType {
+ sea_orm::sea_query::ColumnType::String(sea_orm::sea_query::StringLen::None)
+ }
+}
+
+#[automatically_derived]
+impl sea_orm::sea_query::Nullable for Tag {
+ fn null() -> sea_orm::Value {
+ sea_orm::Value::String(None)
+ }
+}
+```
+
+
+You can override `from_str` and `to_str` with custom functions, which is especially useful if you're using [`strum::Display`](https://docs.rs/strum/latest/strum/derive.Display.html) and [`strum::EnumString`](https://docs.rs/strum/latest/strum/derive.EnumString.html), or manually implemented methods:
+
+```rust
+#[derive(DeriveValueType)]
+#[sea_orm(value_type = "String", from_str = "Tag::from_str", to_str = "Tag::to_str")]
+pub enum Tag {
+ Color,
+ Grey,
+}
+
+impl Tag {
+ fn from_str(s: &str) -> Result { .. }
+
+ fn to_str(&self) -> &'static str { .. }
+}
+```
+
+## String-like structs
+
+Since `2.0.0`, `DeriveValueType` also supports struct types that can convert to and from strings. It's exactly the same as Enum String.
+
+```rust
+#[derive(Copy, Clone, Debug, PartialEq, Eq, DeriveValueType)]
+#[sea_orm(value_type = "String", column_type = "Text")] // override column type
+pub struct Tag3 {
+ pub i: i64,
+}
+
+impl std::fmt::Display for Tag3 {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.i)
+ }
+}
+
+impl std::str::FromStr for Tag3 {
+ type Err = std::num::ParseIntError;
+
+ fn from_str(s: &str) -> Result {
+ let i: i64 = s.parse()?;
+ Ok(Self { i })
+ }
+}
+```
diff --git a/SeaORM/docs/04-generate-entity/06-entity-first.md b/SeaORM/docs/04-generate-entity/06-entity-first.md
new file mode 100644
index 00000000000..9e410aeb129
--- /dev/null
+++ b/SeaORM/docs/04-generate-entity/06-entity-first.md
@@ -0,0 +1,280 @@
+# Entity First Workflow
+
+:::tip Since `2.0.0`
+:::
+
+## What's Entity first?
+
+SeaORM used to adopt a schema‑first approach: meaning you design database tables and write migration scripts first, then generate entities from that schema.
+
+Entity‑first flips the flow: you hand-write the entity files, and let SeaORM generates the tables and foreign keys for you.
+
+All you have to do is to add the following to your [`main.rs`](https://github.com/SeaQL/sea-orm/blob/master/examples/quickstart/src/main.rs) right after creating the database connection:
+
+```rust
+let db = &Database::connect(db_url).await?;
+// synchronizes database schema with entity definitions
+db.get_schema_registry("my_crate::entity::*").sync(db).await?;
+```
+
+This requires two feature flags `schema-sync` and `entity-registry`, and we're going to explain what they do.
+
+## Entity Registry
+
+:::tip
+
+The "my_crate::entity::*" must match the name of your crate in `Cargo.toml`:
+
+```toml title="Cargo.toml"
+[package]
+name = "my_crate"
+```
+
+Alternatively, you can do the following to get the current crate:
+
+```rust
+// This returns the caller's crate
+db.get_schema_registry(module_path!().split("::").next().unwrap())
+```
+
+:::
+
+The above function `get_schema_registry` unfolds into the following:
+
+```rust
+db.get_schema_builder()
+ .register(comment::Entity)
+ .register(post::Entity)
+ .register(profile::Entity)
+ .register(user::Entity)
+ .sync(db)
+ .await?;
+```
+
+You might be wondering: how can SeaORM recognize my entities when, at compile time, the SeaORM crate itself has no knowledge of them?
+
+Rest assured, there's no source‑file scanning or other hacks involved - this is powered by the brilliant [`inventory`](https://docs.rs/inventory/latest/inventory/) crate. The `inventory` crate works by registering items (called plugins) into linker-collected sections.
+
+At compile-time, each `Entity` module registers itself to the global `inventory` along with their module paths and some metadata. On runtime, SeaORM then filters the Entities you requested and construct a [`SchemaBuilder`](https://docs.rs/sea-orm/2.0.0-rc.15/sea_orm/schema/struct.SchemaBuilder.html).
+
+The `EntityRegistry` is completely optional and just adds extra convenience, it's perfectly fine for you to `register` Entities manually like above.
+
+## Resolving Entity Relations
+
+If you remember from the previous post, you'll notice that `comment` has a foreign key referencing `post`. Since SQLite doesn't allow adding foreign keys after the fact, the `post` table must be created before the `comment` table.
+
+This is where SeaORM shines: it automatically builds a dependency graph from your entities and determines the correct topological order to create the tables, so you don't have to keep track of them in your head.
+
+## Schema Sync in Action
+
+The second feature, `schema-sync`, compares the in‑memory entity definitions with the live database schema, detects missing tables, columns, and keys, and creates them idempotently - no matter how many times you run `sync`, the schema converges to the same state.
+
+Let's walk through the different scenarios:
+
+### Adding Table
+
+Let's say you added a new Entity under `mod.rs`
+
+```rust title="entity/mod.rs"
+//! `SeaORM` Entity, @generated by sea-orm-codegen 2.0.0-rc.14
+
+pub mod prelude;
+
+pub mod post;
+pub mod upvote; // ⬅ new entity module
+..
+```
+
+The next time you `cargo run`, you'll see the following:
+
+```sh
+CREATE TABLE "upvote" ( "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT, .. )
+```
+
+This will create the table along with any foreign keys.
+
+### Adding Columns
+
+```rust title="entity/profile.rs"
+use sea_orm::entity::prelude::*;
+
+#[sea_orm::model]
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "profile")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub picture: String,
+ pub date_of_birth: Option, // ⬅ new column
+ ..
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+```
+
+The next time you `cargo run`, you'll see the following:
+
+```sh
+ALTER TABLE "profile" ADD COLUMN "date_of_birth" timestamp with time zone
+```
+
+How about adding a non-nullable column? You can set a `default_value` or `default_expr`:
+
+```rust
+#[sea_orm(default_value = 0)]
+pub post_count: i32,
+
+// this doesn't work in SQLite
+#[sea_orm(default_expr = "Expr::current_timestamp()")]
+pub updated_at: DateTimeUtc,
+```
+
+### Rename Column
+
+If you only want to rename the field name in code, you can simply remap the column name:
+
+```rust
+pub struct Model {
+ ..
+ #[sea_orm(column_name = "date_of_birth")]
+ pub dob: Option, // ⬅ renamed for brevity
+}
+```
+
+This doesn't involve any schema change.
+
+If you want to actually rename the column, then you have to add a special attribute. Note that you can't simply change the field name, as this will be recognized as adding a new column.
+
+```rust
+pub struct Model {
+ ..
+ #[sea_orm(renamed_from = "date_of_birth")] // ⬅ special annotation
+ pub dob: Option,
+}
+```
+
+The next time you `cargo run`, you'll see the following:
+
+```sh
+ALTER TABLE "profile" RENAME COLUMN "date_of_birth" TO "dob"
+```
+
+Nice, isn't it?
+
+### Add Foreign Key
+
+Let's create a new table with a foreign key:
+
+```rust title="entity/upvote.rs"
+use sea_orm::entity::prelude::*;
+
+#[sea_orm::model]
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "upvote")]
+pub struct Model {
+ #[sea_orm(primary_key, auto_increment = false)]
+ pub post_id: i32,
+ #[sea_orm(belongs_to, from = "post_id", to = "id")]
+ pub post: HasOne,
+ ..
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+```
+
+The next time you `cargo run`, you'll see the following:
+
+```sh
+CREATE TABLE "upvote" (
+ "post_id" integer NOT NULL PRIMARY KEY,
+ ..
+ FOREIGN KEY ("post_id") REFERENCES "post" ("id")
+)
+```
+
+If however, the `post` relation is added after the table has been created, then the foreign key couldn't be created for SQLite. Relational queries would still work, but functions completely client-side.
+
+### Add Unique Key
+
+Now, let's say we've forgotten to add a unique constraint on user name:
+
+```rust title="entity/user.rs"
+use sea_orm::entity::prelude::*;
+
+#[sea_orm::model]
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "user")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ #[sea_orm(unique)] // ⬅ add unique key
+ pub name: String,
+ #[sea_orm(unique)]
+ pub email: String,
+ ..
+}
+```
+
+The next time you `cargo run`, you'll see the following:
+
+```sh
+CREATE UNIQUE INDEX "idx-user-name" ON "user" ("name")
+```
+
+As mentioned in the previous blog post, you'll also get a shorthand method generated on the Entity:
+
+```rust
+user::Entity::find_by_name("Bob")..
+```
+
+### Remove Unique Key
+
+Well, you've changed your mind and want to remove the unique constraint on user name:
+
+```rust
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ // no annotation
+ pub name: String,
+ #[sea_orm(unique)]
+ pub email: String,
+ ..
+}
+```
+
+The next time you `cargo run`, you'll see the following:
+
+```sh
+DROP INDEX "idx-user-name"
+```
+
+### Footnotes
+
+Note that in general schema sync would not attempt to do any destructive actions, so meaning no `DROP` on tables, columns and foreign keys. Dropping index is an exception here.
+
+Every time the application starts, a full schema discovery is performed. This may not be desirable in production, so `sync` is gated behind a feature flag `schema-sync` that can be turned off based on build profile.
+
+## Using `SchemaBuilder` in migrations
+
+You can also use [`SchemaBuilder`](https://docs.rs/sea-orm/2.0.0-rc.15/sea_orm/schema/struct.SchemaBuilder.html) inside migrations:
+
+```rust
+#[async_trait::async_trait]
+impl MigrationTrait for Migration {
+ async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
+ let db = manager.get_connection();
+
+ db.get_schema_builder()
+ .register(note::Entity)
+ .apply(db)
+ .await
+ }
+}
+```
+
+The difference between `apply` and `sync` is, `sync` always check that if the tables / columns already existed, while `apply` does not. So `apply` is intended for the initialization step.
+
+Because the migration system already prevents applying a migration step twice, it's fine to use `apply` inside migrations.
+
+To ensure that the migrations can always be applied in order, one by one, you can create a "time capsule" for the initial migration, where you preserve a copy of the initial version of the entities in a submodule.
diff --git a/SeaORM/docs/05-basic-crud/02-select.md b/SeaORM/docs/05-basic-crud/02-select.md
index 5a1e8861b25..4662eabcd5a 100644
--- a/SeaORM/docs/05-basic-crud/02-select.md
+++ b/SeaORM/docs/05-basic-crud/02-select.md
@@ -29,6 +29,7 @@ In addition to retrieving a model by primary key, you can also retrieve one or m
let chocolate: Vec = Cake::find()
.filter(cake::Column::Name.contains("chocolate"))
.order_by_asc(cake::Column::Name)
+ // shorthand for .order_by(cake::Column::Name, Order::Asc)
.all(db)
.await?;
```
@@ -54,7 +55,7 @@ let fruits: Vec = cheese.find_related(Fruit).all(db).await?;
### Eager Loading
-All related models are loaded at once. This provides minimum database round trips compared to lazy loading.
+All related models are loaded in the same query with join.
#### One to One
@@ -75,11 +76,44 @@ let cake_with_fruits: Vec<(cake::Model, Vec)> = Cake::find()
.await?;
```
-### Batch Loading
+### Entity Loader
-Since 0.11, we introduced a [LoaderTrait](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html) to load related entities in batches.
+You can load related Entities into a nested struct called `ModelEx`.
-Compared to eager loading, it saves bandwidth (consider the one to many case, the one side rows may duplicate) at the cost of one (or two, in the case of many to many) more database roundtrip.
+:::tip Since `2.0.0`
+This requires the `#[sea_orm::model]` or `#[sea_orm::compact_model]` macro on entity definition. Learn more [here](https://www.sea-ql.org/blog/2025-10-20-sea-orm-2.0/).
+:::
+
+```rust
+// join paths:
+// cake -> fruit
+// cake -> cake_filling -> filling
+
+let super_cake = cake::Entity::load()
+ .with(fruit::Entity) // 1-1 uses join
+ .with(filling::Entity) // M-N uses data loader
+ .one(db)
+ .await?
+ .unwrap();
+
+super_cake
+ == cake::ModelEx {
+ id: 12,
+ name: "Black Forest".into(),
+ fruit: Some(fruit::ModelEx {
+ name: "Cherry".into(),
+ }.into()),
+ fillings: vec![filling::ModelEx {
+ name: "Chocolate".into(),
+ }],
+ };
+```
+
+### Model Loader
+
+Use the [LoaderTrait](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html) to load related entities in batches.
+
+Compared to eager loading, it saves bandwidth (consider the one to many case, the one side rows may duplicate) at the cost of one more database query.
#### One to One
@@ -101,11 +135,15 @@ let fruits: Vec> = cakes.load_many(Fruit, db).await?;
#### Many to Many
-Use the [load_many_to_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many_to_many) method. You have to provide the junction Entity.
+Use the same [load_many](https://docs.rs/sea-orm/*/sea_orm/query/trait.LoaderTrait.html#tymethod.load_many) method.
+
+:::tip Since `2.0.0`
+You don't have to provide the junction Entity. It's where SeaORM shines!
+:::
```rust
let cakes: Vec = Cake::find().all(db).await?;
-let fillings: Vec> = cakes.load_many_to_many(Filling, CakeFilling, db).await?;
+let fillings: Vec> = cakes.load_many(Filling, db).await?;
```
## Paginate Result
@@ -146,7 +184,7 @@ for cake in cursor.last(10).all(db).await? {
}
```
-Paginate rows based on a composite primary key is also available.
+Paginate rows based on a composite primary keys are also supported.
```rust
use sea_orm::{entity::*, query::*, tests_cfg::cake_filling};
@@ -159,6 +197,24 @@ let rows = cake_filling::Entity::find()
.await?;
```
-## Select custom
+## Select Partial Model
-If you want to select custom columns and expressions, read the [custom select](08-advanced-query/01-custom-select.md) section.
+If you want to select just a subset of columns, you can define a Partial Model.
+
+```rust
+use sea_orm::DerivePartialModel;
+
+#[derive(DerivePartialModel)]
+#[sea_orm(entity = "cake::Entity")]
+struct CakeWithFruit {
+ name: String,
+ #[sea_orm(nested)]
+ fruit: Option, // this can be a regular or another partial model
+}
+
+let cakes: Vec = Cake::find()
+ .left_join(fruit::Entity)
+ .into_partial_model() // only the columns in the partial model will be selected
+ .all(db)
+ .await?;
+```
\ No newline at end of file
diff --git a/SeaORM/docs/05-basic-crud/03-active-model.md b/SeaORM/docs/05-basic-crud/03-active-model.md
new file mode 100644
index 00000000000..33508679f71
--- /dev/null
+++ b/SeaORM/docs/05-basic-crud/03-active-model.md
@@ -0,0 +1,128 @@
+# ActiveModel
+
+Before diving into insert and update operations we have to introduce `ActiveValue` and `ActiveModel`.
+
+## ActiveValue
+
+The state of a field in an `ActiveModel`.
+
+There are three possible states represented by three enum variants:
+
+- `Set` - A `Value` that's explicitly set by the application and sent to the database.
+ Use this to insert or set a specific value.
+
+ When editing an existing value, you can use [`set_if_not_equal`](https://docs.rs/sea-orm/*/sea_orm/entity/enum.ActiveValue.html#method.set_if_not_equals)
+ to preserve the `Unchanged` state when the new value is the same as the old one.
+ Then you can meaningfully use methods like [`ActiveModelTrait::is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.ActiveModelTrait.html#method.is_changed).
+- `Unchanged` - An existing, unchanged `Value` from the database.
+
+ You get these when you query an existing `Model`
+ from the database and convert it into an `ActiveModel`.
+- `NotSet` - An undefined `Value`. Nothing is sent to the database.
+
+ When you create a new `ActiveModel`, its fields are `NotSet` by default.
+
+ This can be useful when:
+
+ - You insert a new record and want the database to generate a default value (e.g., an id).
+ - In an `UPDATE` statement, you don't want to update some fields.
+
+The difference between these states is useful
+when constructing `INSERT` and `UPDATE` SQL statements (see examples below).
+It's also useful for knowing which fields have changed in a record.
+
+### Examples
+
+```rust
+use sea_orm::tests_cfg::{cake, fruit};
+use sea_orm::{DbBackend, entity::*, query::*};
+
+// Here, we use `NotSet` to let the database automatically generate an `id`.
+// This is different from `Set(None)` that explicitly sets `cake_id` to `NULL`.
+assert_eq!(
+ Insert::one(fruit::ActiveModel {
+ id: ActiveValue::NotSet,
+ name: ActiveValue::Set("Orange".to_owned()),
+ cake_id: ActiveValue::Set(None),
+ })
+ .build(DbBackend::Postgres)
+ .to_string(),
+ r#"INSERT INTO "fruit" ("name", "cake_id") VALUES ('Orange', NULL)"#
+);
+
+// Here, we update the record, set `cake_id` to the new value
+// and use `NotSet` to avoid updating the `name` field.
+// `id` is the primary key, so it's used in the condition and not updated.
+assert_eq!(
+ Update::one(fruit::ActiveModel {
+ id: ActiveValue::Unchanged(1),
+ name: ActiveValue::NotSet,
+ cake_id: ActiveValue::Set(Some(2)),
+ })
+ .validate()? // <- required in 2.0
+ .build(DbBackend::Postgres)
+ .to_string(),
+ r#"UPDATE "fruit" SET "cake_id" = 2 WHERE "fruit"."id" = 1"#
+);
+```
+
+## ActiveModel
+
+An `ActiveModel` has all the attributes of `Model` wrapped in `ActiveValue`.
+
+You can use `ActiveModel` to insert a row with a subset of columns set.
+
+```rust
+let cheese: Option = Cake::find_by_id(1).one(db).await?;
+
+// Get Model
+let model: cake::Model = cheese.unwrap();
+assert_eq!(model.name, "Cheese Cake".to_owned());
+
+// Into ActiveModel
+let active_model: cake::ActiveModel = model.into();
+assert_eq!(active_model.name, ActiveValue::unchanged("Cheese Cake".to_owned()));
+```
+
+### Checking if an ActiveModel is changed
+
+You can check whether any field in an `ActiveModel` is `Set` with the [`is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ActiveModelTrait.html#method.is_changed) method.
+
+```rust
+let mut fruit: fruit::ActiveModel = Default::default();
+assert!(!fruit.is_changed());
+
+fruit.set(fruit::Column::Name, "apple".into());
+assert!(fruit.is_changed());
+```
+
+### Convert ActiveModel back to Model
+
+Using [`try_into_model`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.TryIntoModel.html#tymethod.try_into_model) method you can convert ActiveModel back to Model.
+
+```rust
+assert_eq!(
+ ActiveModel {
+ id: Set(2),
+ name: Set("Apple".to_owned()),
+ cake_id: Set(Some(1)),
+ }
+ .try_into_model()
+ .unwrap(),
+ Model {
+ id: 2,
+ name: "Apple".to_owned(),
+ cake_id: Some(1),
+ }
+);
+
+assert_eq!(
+ ActiveModel {
+ id: Set(1),
+ name: NotSet,
+ cake_id: Set(None),
+ }
+ .try_into_model(),
+ Err(DbErr::AttrNotSet(String::from("name")))
+);
+```
diff --git a/SeaORM/docs/05-basic-crud/03-insert.md b/SeaORM/docs/05-basic-crud/04-insert.md
similarity index 55%
rename from SeaORM/docs/05-basic-crud/03-insert.md
rename to SeaORM/docs/05-basic-crud/04-insert.md
index 41b1c533e9f..b50ccbc1bb6 100644
--- a/SeaORM/docs/05-basic-crud/03-insert.md
+++ b/SeaORM/docs/05-basic-crud/04-insert.md
@@ -1,148 +1,5 @@
# Insert
-Before diving into SeaORM insert we have to introduce `ActiveValue` and `ActiveModel`.
-
-## ActiveValue
-
-A wrapper struct to capture the changes made to `ActiveModel` attributes.
-
-```rust
-use sea_orm::ActiveValue::{Set, NotSet, Unchanged};
-
-// Set value
-let _: ActiveValue = Set(10);
-
-// NotSet value
-let _: ActiveValue = NotSet;
-
-// An `Unchanged` value
-let v: ActiveValue = Unchanged(10);
-
-// Convert `Unchanged` active value as `Set`
-assert!(v.reset(), Set(10));
-```
-
-## Model & ActiveModel
-
-An `ActiveModel` has all the attributes of `Model` wrapped in `ActiveValue`.
-
-You can use `ActiveModel` to insert a row with a subset of columns set.
-
-```rust
-let cheese: Option = Cake::find_by_id(1).one(db).await?;
-
-// Get Model
-let model: cake::Model = cheese.unwrap();
-assert_eq!(model.name, "Cheese Cake".to_owned());
-
-// Into ActiveModel
-let active_model: cake::ActiveModel = model.into();
-assert_eq!(active_model.name, ActiveValue::unchanged("Cheese Cake".to_owned()));
-```
-
-### Set ActiveModel from JSON Value
-
-If you want to save user input into the database you can easily convert JSON value into `ActiveModel`. Note that you might want to [skip deserializing](https://serde.rs/attr-skip-serializing.html) JSON's primary key attribute, you can config that as shown below.
-
-```rust
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
-#[sea_orm(table_name = "fruit")]
-pub struct Model {
- #[sea_orm(primary_key)]
- #[serde(skip_deserializing)] // Skip deserializing
- pub id: i32,
- pub name: String,
- pub cake_id: Option,
-}
-```
-
-Set the attributes in `ActiveModel` with `set_from_json` method.
-
-```rust
-// A ActiveModel with primary key set
-let mut fruit = fruit::ActiveModel {
- id: ActiveValue::Set(1),
- name: ActiveValue::NotSet,
- cake_id: ActiveValue::NotSet,
-};
-
-// Note that this method will not alter the primary key values in ActiveModel
-fruit.set_from_json(json!({
- "id": 8,
- "name": "Apple",
- "cake_id": 1,
-}))?;
-
-assert_eq!(
- fruit,
- fruit::ActiveModel {
- id: ActiveValue::Set(1),
- name: ActiveValue::Set("Apple".to_owned()),
- cake_id: ActiveValue::Set(Some(1)),
- }
-);
-```
-
-Create a new `ActiveModel` from JSON value with the `from_json` method.
-
-```rust
-let fruit = fruit::ActiveModel::from_json(json!({
- "name": "Apple",
-}))?;
-
-assert_eq!(
- fruit,
- fruit::ActiveModel {
- id: ActiveValue::NotSet,
- name: ActiveValue::Set("Apple".to_owned()),
- cake_id: ActiveValue::NotSet,
- }
-);
-```
-
-### Checking if an ActiveModel is changed
-
-You can check whether any field in an `ActiveModel` is `Set` with the [`is_changed`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ActiveModelTrait.html#method.is_changed) method.
-
-```rust
-let mut fruit: fruit::ActiveModel = Default::default();
-assert!(!fruit.is_changed());
-
-fruit.set(fruit::Column::Name, "apple".into());
-assert!(fruit.is_changed());
-```
-
-### Convert ActiveModel back to Model
-
-Using [try_into_model](https://docs.rs/sea-orm/*/sea_orm/entity/trait.TryIntoModel.html#tymethod.try_into_model) method you can convert ActiveModel back to Model.
-
-```rust
-assert_eq!(
- ActiveModel {
- id: Set(2),
- name: Set("Apple".to_owned()),
- cake_id: Set(Some(1)),
- }
- .try_into_model()
- .unwrap(),
- Model {
- id: 2,
- name: "Apple".to_owned(),
- cake_id: Some(1),
- }
-);
-
-assert_eq!(
- ActiveModel {
- id: Set(1),
- name: NotSet,
- cake_id: Set(None),
- }
- .try_into_model(),
- Err(DbErr::AttrNotSet(String::from("name")))
-);
-```
-
## Insert One
Insert an active model and get back a fresh `Model`. Its value is retrieved from database, so any auto-generated fields will be populated.
@@ -297,4 +154,110 @@ let res = Entity::insert_many([..])
.await;
assert!(matches!(res, Ok(TryInsertResult::Conflicted)));
+```
+
+### MySQL support
+
+Set `ON CONFLICT` on primary key `DO NOTHING`, but with MySQL specific polyfill.
+
+```rust
+let orange = cake::ActiveModel {
+ id: ActiveValue::set(2),
+ name: ActiveValue::set("Orange".to_owned()),
+};
+
+assert_eq!(
+ cake::Entity::insert(orange.clone())
+ .on_conflict_do_nothing()
+ .build(DbBackend::MySql)
+ .to_string(),
+ r#"INSERT INTO `cake` (`id`, `name`) VALUES (2, 'Orange') ON DUPLICATE KEY UPDATE `id` = `id`"#,
+);
+assert_eq!(
+ cake::Entity::insert(orange.clone())
+ .on_conflict_do_nothing()
+ .build(DbBackend::Postgres)
+ .to_string(),
+ r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("id") DO NOTHING"#,
+);
+assert_eq!(
+ cake::Entity::insert(orange)
+ .on_conflict_do_nothing()
+ .build(DbBackend::Sqlite)
+ .to_string(),
+ r#"INSERT INTO "cake" ("id", "name") VALUES (2, 'Orange') ON CONFLICT ("id") DO NOTHING"#,
+);
+```
+
+## Returning Inserted Models
+
+:::tip Since `2.0.0`
+
+`Entity::insert` and `Entity::insert_many` are now separate types, the `exec_with_returning` method have the appropriate return type. So `exec_with_returning_many` is now deprecated.
+
+:::
+
+Supported by Postgres and SQLite, the following returns the newly inserted models after insert.
+
+```rust
+assert_eq!(
+ cake::Entity::insert(cake::ActiveModel {
+ id: NotSet,
+ name: Set("Apple Pie".to_owned()),
+ })
+ .exec_with_returning(&db)
+ .await?,
+ cake::Model {
+ id: 1,
+ name: "Apple Pie".to_owned(),
+ }
+);
+```
+
+```rust
+assert_eq!(
+ cake::Entity::insert_many([
+ cake::ActiveModel {
+ id: NotSet,
+ name: Set("Apple Pie".to_owned()),
+ },
+ cake::ActiveModel {
+ id: NotSet,
+ name: Set("Choco Pie".to_owned()),
+ },
+ ])
+ .exec_with_returning(&db)
+ .await?,
+ [
+ cake::Model {
+ id: 1,
+ name: "Apple Pie".to_owned(),
+ },
+ cake::Model {
+ id: 2,
+ name: "Choco Pie".to_owned(),
+ }
+ ]
+);
+```
+
+There is also a `exec_with_returning_keys` if you only need the primary keys after insert.
+
+```rust
+assert_eq!(
+ cakes_bakers::Entity::insert_many([
+ cakes_bakers::ActiveModel {
+ cake_id: Set(1),
+ baker_id: Set(2),
+ },
+ cakes_bakers::ActiveModel {
+ cake_id: Set(2),
+ baker_id: Set(1),
+ },
+ ])
+ .exec_with_returning_keys(db)
+ .await
+ .unwrap(),
+ [(1, 2), (2, 1)]
+);
```
\ No newline at end of file
diff --git a/SeaORM/docs/05-basic-crud/04-update.md b/SeaORM/docs/05-basic-crud/05-update.md
similarity index 78%
rename from SeaORM/docs/05-basic-crud/04-update.md
rename to SeaORM/docs/05-basic-crud/05-update.md
index 433d3e27993..914106abfe8 100644
--- a/SeaORM/docs/05-basic-crud/04-update.md
+++ b/SeaORM/docs/05-basic-crud/05-update.md
@@ -43,7 +43,7 @@ You can also update multiple rows in the database without finding each `Model` w
// Bulk set attributes using ActiveModel
let update_result: UpdateResult = Fruit::update_many()
.set(pear)
- .filter(fruit::Column::Id.eq(1))
+ .filter(fruit::Column::Id.is_in(vec![1]))
.exec(db)
.await?;
@@ -55,9 +55,19 @@ Fruit::update_many()
.await?;
```
-### Update with returning (Postgres only)
+:::tip Since `2.0.0`
-Use `exec_with_returning` to return models that were modified:
+Added `ColumnTrait::eq_any` as a shorthand for the ` = ANY` operator. Postgres only.
+
+```rust
+Fruit::update_many().filter(fruit::Column::Id.eq_any(vec![2, 3]))
+```
+
+:::
+
+## Returning Updated Models
+
+Postgres and SQLite only, MariaDB requires the `mariadb-use-returning` feature flag.
```rust
let fruits: Vec = Fruit::update_many()
@@ -65,4 +75,13 @@ let fruits: Vec = Fruit::update_many()
.filter(fruit::Column::Name.contains("Apple"))
.exec_with_returning(db)
.await?;
+
+assert_eq!(
+ fruits[0],
+ fruit::Model {
+ id: 2,
+ name: "Apple".to_owned(),
+ cake_id: Some(1),
+ }
+);
```
\ No newline at end of file
diff --git a/SeaORM/docs/05-basic-crud/05-save.md b/SeaORM/docs/05-basic-crud/06-save.md
similarity index 100%
rename from SeaORM/docs/05-basic-crud/05-save.md
rename to SeaORM/docs/05-basic-crud/06-save.md
diff --git a/SeaORM/docs/05-basic-crud/06-delete.md b/SeaORM/docs/05-basic-crud/07-delete.md
similarity index 62%
rename from SeaORM/docs/05-basic-crud/06-delete.md
rename to SeaORM/docs/05-basic-crud/07-delete.md
index e49f2d33ac9..cb3926752f8 100644
--- a/SeaORM/docs/05-basic-crud/06-delete.md
+++ b/SeaORM/docs/05-basic-crud/07-delete.md
@@ -36,3 +36,31 @@ let res: DeleteResult = fruit::Entity::delete_many()
assert_eq!(res.rows_affected, 2);
```
+
+## Returning Deleted Models
+
+Postgres and SQLite only, MariaDB requires the `mariadb-use-returning` feature flag.
+
+```rust
+assert_eq!(
+ fruit::Entity::delete(ActiveModel {
+ id: Set(3),
+ ..Default::default()
+ })
+ .exec_with_returning(db)
+ .await?,
+ Some(fruit::Model {
+ id: 3,
+ name: "Apple".to_owned(),
+ })
+);
+```
+
+```rust
+let deleted_models: Vec = order::Entity::delete_many()
+ .filter(order::Column::CustomerId.eq(22))
+ .exec_with_returning(db)
+ .await?
+
+assert_eq!(deleted_models.len(), 2); // two items deleted
+```
\ No newline at end of file
diff --git a/SeaORM/docs/05-basic-crud/07-json.md b/SeaORM/docs/05-basic-crud/07-json.md
deleted file mode 100644
index 5778e1e43c5..00000000000
--- a/SeaORM/docs/05-basic-crud/07-json.md
+++ /dev/null
@@ -1,54 +0,0 @@
-# JSON
-
-## Select JSON Result
-
-All SeaORM selects are capable of returning `serde_json::Value`.
-
-```rust
-// Find by id
-let cake: Option = Cake::find_by_id(1)
- .into_json()
- .one(db)
- .await?;
-
-assert_eq!(
- cake,
- Some(serde_json::json!({
- "id": 1,
- "name": "Cheese Cake"
- }))
-);
-
-// Find with filter
-let cakes: Vec = Cake::find()
- .filter(cake::Column::Name.contains("chocolate"))
- .order_by_asc(cake::Column::Name)
- .into_json()
- .all(db)
- .await?;
-
-assert_eq!(
- cakes,
- [
- serde_json::json!({
- "id": 2,
- "name": "Chocolate Forest"
- }),
- serde_json::json!({
- "id": 8,
- "name": "Chocolate Cupcake"
- }),
- ]
-);
-
-// Paginate json result
-let cake_pages: Paginator<_> = Cake::find()
- .filter(cake::Column::Name.contains("chocolate"))
- .order_by_asc(cake::Column::Name)
- .into_json()
- .paginate(db, 50);
-
-while let Some(cakes) = cake_pages.fetch_and_next().await? {
- // Do something on cakes: Vec
-}
-```
diff --git a/SeaORM/docs/05-basic-crud/08-json.md b/SeaORM/docs/05-basic-crud/08-json.md
new file mode 100644
index 00000000000..71834645a2c
--- /dev/null
+++ b/SeaORM/docs/05-basic-crud/08-json.md
@@ -0,0 +1,131 @@
+# JSON
+
+## Select JSON Result
+
+All SeaORM selects are capable of returning `serde_json::Value`.
+
+```rust
+// Find by id
+let cake: Option = Cake::find_by_id(1)
+ .into_json()
+ .one(db)
+ .await?;
+
+assert_eq!(
+ cake,
+ Some(serde_json::json!({
+ "id": 1,
+ "name": "Cheese Cake"
+ }))
+);
+
+// Find with filter
+let cakes: Vec = Cake::find()
+ .filter(cake::Column::Name.contains("chocolate"))
+ .order_by_asc(cake::Column::Name)
+ .into_json()
+ .all(db)
+ .await?;
+
+assert_eq!(
+ cakes,
+ [
+ serde_json::json!({
+ "id": 2,
+ "name": "Chocolate Forest"
+ }),
+ serde_json::json!({
+ "id": 8,
+ "name": "Chocolate Cupcake"
+ }),
+ ]
+);
+
+// Paginate json result
+let cake_pages: Paginator<_> = Cake::find()
+ .filter(cake::Column::Name.contains("chocolate"))
+ .order_by_asc(cake::Column::Name)
+ .into_json()
+ .paginate(db, 50);
+
+while let Some(cakes) = cake_pages.fetch_and_next().await? {
+ // Do something on cakes: Vec
+}
+```
+
+## Select JSON from raw SQL
+
+```rust
+let result: Vec = JsonValue::find_by_statement(Statement::from_sql_and_values(
+ DbBackend::Postgres,
+ r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#,
+ [],
+ ))
+ .all(&db)
+ .await?;
+```
+
+## Convert JSON to ActiveModel
+
+If you want to save user input into the database you can easily convert JSON value into `ActiveModel`. You might want to [skip deserializing](https://serde.rs/attr-skip-serializing.html) some of the unwanted attributes.
+
+:::tip Since `2.0.0`
+
+Not all fields of the Model need to be present in the JSON input, undefined fields will simply become `ActiveValue::NotSet`.
+
+:::
+
+```rust
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel, Serialize, Deserialize)]
+#[sea_orm(table_name = "fruit")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ pub cake_id: Option,
+}
+```
+
+Set the attributes in `ActiveModel` with `set_from_json` method.
+
+```rust
+// An ActiveModel with primary key set
+let mut fruit = fruit::ActiveModel {
+ id: ActiveValue::Set(1),
+ name: ActiveValue::NotSet,
+ cake_id: ActiveValue::NotSet,
+};
+
+// Note that this method will not alter the primary key values in ActiveModel
+fruit.set_from_json(json!({
+ "id": 8,
+ "name": "Apple",
+ "cake_id": 1,
+}))?;
+
+assert_eq!(
+ fruit,
+ fruit::ActiveModel {
+ id: ActiveValue::Set(1),
+ name: ActiveValue::Set("Apple".to_owned()),
+ cake_id: ActiveValue::Set(Some(1)),
+ }
+);
+```
+
+You can also create a new `ActiveModel` from JSON value with the `from_json` method.
+
+```rust
+let fruit = fruit::ActiveModel::from_json(json!({
+ "name": "Apple",
+}))?;
+
+assert_eq!(
+ fruit,
+ fruit::ActiveModel {
+ id: ActiveValue::NotSet,
+ name: ActiveValue::Set("Apple".to_owned()),
+ cake_id: ActiveValue::NotSet,
+ }
+);
+```
\ No newline at end of file
diff --git a/SeaORM/docs/05-basic-crud/08-raw-sql.md b/SeaORM/docs/05-basic-crud/09-raw-sql.md
similarity index 56%
rename from SeaORM/docs/05-basic-crud/08-raw-sql.md
rename to SeaORM/docs/05-basic-crud/09-raw-sql.md
index 4663267d3d5..49a8dfd0eb1 100644
--- a/SeaORM/docs/05-basic-crud/08-raw-sql.md
+++ b/SeaORM/docs/05-basic-crud/09-raw-sql.md
@@ -1,75 +1,89 @@
# Raw SQL
-## Query by raw SQL
+:::tip Since `2.0.0`
-You can select `Model` from raw query, with appropriate syntax for binding parameters, i.e. `?` for MySQL and SQLite, and `$N` for PostgreSQL.
+A new macro `raw_sql` is added, with many neat features to make writing raw SQL queries more ergononmic.
+
+In particular, you can expand arrays with `({..ids})` into `(?, ?, ?)`.
+
+Learn more in [SeaQuery just made writing raw SQL more enjoyable](https://www.sea-ql.org/blog/2025-08-15-sea-query-raw-sql/).
+
+:::
+
+## Find Model by raw SQL
```rust
-let cheese: Option = cake::Entity::find()
- .from_raw_sql(Statement::from_sql_and_values(
- DbBackend::Postgres,
- r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#,
- [1.into()],
+let id = 1;
+
+let cake: Option = cake::Entity::find()
+ .from_raw_sql(raw_sql!(
+ Postgres,
+ r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = {id}"#
))
.one(&db)
.await?;
```
-You can also select a custom model. Here, we select all unique names from cake.
+## Select into custom struct by raw SQL
+
+Here nested select is also demonstrated.
```rust
-#[derive(Debug, FromQueryResult)]
-pub struct UniqueCake {
+#[derive(FromQueryResult)]
+struct Cake {
name: String,
+ #[sea_orm(nested)]
+ bakery: Option,
}
-let unique: Vec = UniqueCake::find_by_statement(Statement::from_sql_and_values(
- DbBackend::Postgres,
- r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#,
- [],
- ))
- .all(&db)
- .await?;
-```
+#[derive(FromQueryResult)]
+struct Bakery {
+ #[sea_orm(alias = "bakery_name")]
+ name: String,
+}
-If you do not know what your model looks like beforehand, you can use `JsonValue`.
+let cake_ids = [2, 3, 4];
+
+let cake: Option = Cake::find_by_statement(raw_sql!(
+ Sqlite,
+ r#"SELECT "cake"."name", "bakery"."name" AS "bakery_name"
+ FROM "cake"
+ LEFT JOIN "bakery" ON "cake"."bakery_id" = "bakery"."id"
+ WHERE "cake"."id" IN ({..cake_ids})"#
+))
+.one(db)
+.await?;
+```
-```rust
-let unique: Vec = JsonValue::find_by_statement(Statement::from_sql_and_values(
- DbBackend::Postgres,
- r#"SELECT "cake"."name" FROM "cake" GROUP BY "cake"."name"#,
- [],
- ))
- .all(&db)
- .await?;
- ```
+## Paginate raw SQL query
You can paginate [`SelectorRaw`](https://docs.rs/sea-orm/*/sea_orm/struct.SelectorRaw.html) and fetch `Model` in batch.
```rust
+let ids = vec![1, 2, 3, 4];
+
let mut cake_pages = cake::Entity::find()
- .from_raw_sql(Statement::from_sql_and_values(
- DbBackend::Postgres,
- r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" = $1"#,
- [1.into()],
+ .from_raw_sql(raw_sql!(
+ Postgres,
+ r#"SELECT "cake"."id", "cake"."name" FROM "cake" WHERE "id" IN ({..ids})"#
))
- .paginate(db, 50);
-
+ .paginate(db, 10);
+
while let Some(cakes) = cake_pages.fetch_and_next().await? {
// Do something on cakes: Vec
}
```
-## Get raw SQL query
+## Inspect raw SQL from queries
Use `build` and `to_string` methods on any CRUD operations to get the database-specific raw SQL for debugging purposes.
```rust
-use sea_orm::DatabaseBackend;
+use sea_orm::{DbBackend, QueryTrait};
assert_eq!(
cake_filling::Entity::find_by_id((6, 8))
- .build(DatabaseBackend::MySql)
+ .build(DbBackend::MySql)
.to_string(),
[
"SELECT `cake_filling`.`cake_id`, `cake_filling`.`filling_id` FROM `cake_filling`",
@@ -86,8 +100,8 @@ You can build SQL statements using `sea-query` and query / execute it directly o
```rust
let query_res: Option = db
- .query_one(Statement::from_string(
- DatabaseBackend::MySql,
+ .query_one_raw(Statement::from_string(
+ DbBackend::MySql,
"SELECT * FROM `cake`;",
))
.await?;
@@ -95,8 +109,8 @@ let query_res = query_res.unwrap();
let id: i32 = query_res.try_get("", "id")?;
let query_res_vec: Vec = db
- .query_all(Statement::from_string(
- DatabaseBackend::MySql,
+ .query_all_raw(Statement::from_string(
+ DbBackend::MySql,
"SELECT * FROM `cake`;",
))
.await?;
@@ -106,8 +120,8 @@ let query_res_vec: Vec = db
```rust
let exec_res: ExecResult = db
- .execute(Statement::from_string(
- DatabaseBackend::MySql,
+ .execute_raw(Statement::from_string(
+ DbBackend::MySql,
"DROP DATABASE IF EXISTS `sea`;",
))
.await?;
diff --git a/SeaORM/docs/06-relation/01-one-to-one.md b/SeaORM/docs/06-relation/01-one-to-one.md
index 66f5ee1a7d9..b64f905f382 100644
--- a/SeaORM/docs/06-relation/01-one-to-one.md
+++ b/SeaORM/docs/06-relation/01-one-to-one.md
@@ -1,7 +1,8 @@
# One to One
-:::tip We need your help! 📝
-Thank you for reading this documentation. While we have you, would you spare a few minutes into completing our [SeaQL Community Survey](https://www.sea-ql.org/community-survey)? This is essential for the continued development and maintenance of SeaORM.
+:::tip Rustacean Sticker Pack 🦀
+[Our stickers](https://www.sea-ql.org/sticker-pack/) are made with a premium water-resistant vinyl with a unique matte finish.
+Stick them on your laptop, notebook, or any gadget to show off your love for Rust!
:::
A one-to-one relation is the most basic type of database relation. Let say a `Cake` entity has at most one `Fruit` topping.
@@ -9,84 +10,70 @@ A one-to-one relation is the most basic type of database relation. Let say a `Ca
## Defining the Relation
On the `Cake` entity, to define the relation:
-1. Add a new variant `Fruit` to the `Relation` enum.
-1. Define it with `Entity::has_one()`.
-1. Implement the `Related` trait.
-
-```rust {3,9,14} title="entity/cake.rs"
-#[derive(Copy, Clone, Debug, EnumIter)]
-pub enum Relation {
- Fruit,
-}
-
-impl RelationTrait for Relation {
- fn def(&self) -> RelationDef {
- match self {
- Self::Fruit => Entity::has_one(super::fruit::Entity).into(),
- }
- }
-}
-
-impl Related for Entity {
- fn to() -> RelationDef {
- Relation::Fruit.def()
- }
+1. Add a new field `fruit` to the `Model`.
+1. Annotate it with `has_one`.
+
+```rust {7,8} title="entity/cake.rs"
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "cake")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ #[sea_orm(has_one)]
+ pub fruit: HasOne,
}
```
-Alternatively, the definition can be shortened by the `DeriveRelation` macro,
-where the following eliminates the need for the `RelationTrait` implementation above:
+
+ It's expanded to:
-```rust
+```rust {3,4,9} title="entity/cake.rs"
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_one = "super::fruit::Entity")]
Fruit,
}
-// `Related` trait has to be implemented by hand
impl Related for Entity {
fn to() -> RelationDef {
Relation::Fruit.def()
}
}
```
+
## Defining the Inverse Relation
On the `Fruit` entity, its `cake_id` attribute is referencing the primary key of `Cake` entity.
-To define the inverse relation:
-1. Add a new enum variant `Relation::Cake` to the `Fruit` entity.
-1. Write the definition of it with the `Entity::belongs_to()` method, we always define the inverse relation using this method.
-1. Implement the `Related` trait.
+:::tip
-```rust title="entity/fruit.rs"
-#[derive(Copy, Clone, Debug, EnumIter)]
-pub enum Relation {
- Cake,
-}
+The rule of thumb is, always define a `belongs_to` on the Entity with a foreign key `xxx_id`.
-impl RelationTrait for Relation {
- fn def(&self) -> RelationDef {
- match self {
- Self::Cake => Entity::belongs_to(super::cake::Entity)
- .from(Column::CakeId)
- .to(super::cake::Column::Id)
- .into(),
- }
- }
-}
+:::
-impl Related for Entity {
- fn to() -> RelationDef {
- Relation::Cake.def()
- }
+To define the inverse relation:
+1. Add a new field `cake` to the fruit `Model`.
+1. Annotate the relation with `belongs_to`.
+1. Implement the `Related` trait.
+
+```rust {9,10} title="entity/fruit.rs"
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "fruit")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ #[sea_orm(unique)]
+ pub cake_id: Option,
+ #[sea_orm(belongs_to, from = "cake_id", to = "id")]
+ pub cake: HasOne,
}
```
-Alternatively, the definition can be shortened by the `DeriveRelation` macro,
-where the following eliminates the need for the `RelationTrait` implementation above:
+
+ It's expanded to:
```rust
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@@ -99,10 +86,10 @@ pub enum Relation {
Cake,
}
-// `Related` trait has to be implemented by hand
impl Related for Entity {
fn to() -> RelationDef {
Relation::Cake.def()
}
}
```
+
\ No newline at end of file
diff --git a/SeaORM/docs/06-relation/02-one-to-many.md b/SeaORM/docs/06-relation/02-one-to-many.md
index 979bb000a27..e0924e8b0aa 100644
--- a/SeaORM/docs/06-relation/02-one-to-many.md
+++ b/SeaORM/docs/06-relation/02-one-to-many.md
@@ -4,77 +4,58 @@ A one-to-many relation is similar to a one-to-one relation. In the previous sect
## Defining the Relation
-This is almost identical to defining a one-to-one relation; the only difference is that we use `Entity::has_many()` method here.
-
-```rust {3,9,14} title="entity/cake.rs"
-#[derive(Copy, Clone, Debug, EnumIter)]
-pub enum Relation {
- Fruit,
-}
-
-impl RelationTrait for Relation {
- fn def(&self) -> RelationDef {
- match self {
- Self::Fruit => Entity::has_many(super::fruit::Entity).into(),
- }
- }
-}
-
-impl Related for Entity {
- fn to() -> RelationDef {
- Relation::Fruit.def()
- }
+This is almost identical to defining a one-to-one relation; the only difference is that we use `has_many` annotation here.
+
+```rust {7,8} title="entity/cake.rs"
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "cake")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ #[sea_orm(has_many)]
+ pub fruit: HasMany,
}
```
-Alternatively, the definition can be shortened by the `DeriveRelation` macro,
-where the following eliminates the need for the `RelationTrait` implementation above:
+
+ It's expanded to:
-```rust
+```rust {3,4,9}
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
pub enum Relation {
#[sea_orm(has_many = "super::fruit::Entity")]
Fruit,
}
-// `Related` trait has to be implemented by hand
impl Related for Entity {
fn to() -> RelationDef {
Relation::Fruit.def()
}
}
```
+
## Defining the Inverse Relation
-It is the same as defining the one-to-one inverse relation.
-
-```rust title="entity/fruit.rs"
-#[derive(Copy, Clone, Debug, EnumIter)]
-pub enum Relation {
- Cake,
-}
-
-impl RelationTrait for Relation {
- fn def(&self) -> RelationDef {
- match self {
- Self::Cake => Entity::belongs_to(super::cake::Entity)
- .from(Column::CakeId)
- .to(super::cake::Column::Id)
- .into(),
- }
- }
-}
-
-impl Related for Entity {
- fn to() -> RelationDef {
- Relation::Cake.def()
- }
+It is the same as defining the one-to-one inverse relation, just without a unique key. The rule of thumb is, always define a `belongs_to` on the Entity with a foreign key `xxx_id`.
+
+```rust {9,10} title="entity/fruit.rs"
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "fruit")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ // without unique
+ pub cake_id: Option,
+ #[sea_orm(belongs_to, from = "cake_id", to = "id")]
+ pub cake: HasOne,
}
```
-Alternatively, the definition can be shortened by the `DeriveRelation` macro,
-where the following eliminates the need for the `RelationTrait` implementation above:
+
+ It's expanded to:
```rust
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@@ -87,10 +68,58 @@ pub enum Relation {
Cake,
}
-// `Related` trait has to be implemented by hand
impl Related for Entity {
fn to() -> RelationDef {
Relation::Cake.def()
}
}
```
+
+
+## Composite Foreign Key
+
+Composite foreign key is supported using a tuple syntax.
+
+```rust title="composite_a.rs"
+#[sea_orm::model]
+#[sea_orm(table_name = "composite_a")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ #[sea_orm(unique_key = "pair")]
+ pub left_id: i32,
+ #[sea_orm(unique_key = "pair")]
+ pub right_id: i32,
+ #[sea_orm(has_one)]
+ pub b: Option,
+}
+```
+
+```rust title="composite_b.rs"
+#[sea_orm::model]
+#[sea_orm(table_name = "composite_b")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub left_id: i32,
+ pub right_id: i32,
+ #[sea_orm(belongs_to, from = "(left_id, right_id)", to = "(left_id, right_id)")]
+ pub a: Option,
+}
+```
+
+
+ It's expanded to:
+
+```rust title="composite_b.rs"
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+ #[sea_orm(
+ belongs_to = "super::composite_a::Entity",
+ from = "(Column::LeftId, Column::RightId)",
+ to = "(super::composite_a::Column::LeftId, super::composite_a::Column::RightId)",
+ )]
+ CakeFilling,
+}
+```
+
\ No newline at end of file
diff --git a/SeaORM/docs/06-relation/03-many-to-many.md b/SeaORM/docs/06-relation/03-many-to-many.md
index 2f3a7384704..8c52cb949ca 100644
--- a/SeaORM/docs/06-relation/03-many-to-many.md
+++ b/SeaORM/docs/06-relation/03-many-to-many.md
@@ -1,10 +1,32 @@
# Many to Many
+A standout feature of SeaORM is its ability to model many-to-many relationships directly at the Entity level. The intermediate junction table is abstracted away, so traversing an M-N relation feels just like a simple 1-N: a single method call instead of multiple joins.
+
A many-to-many relation is formed by three tables, where two tables are related via a junction table. As an example, a `Cake` has many `Filling` and `Filling` are shared by many `Cake` via an intermediate entity `CakeFilling`.
## Defining the Relation
-On the `Cake` entity, implement the `Related` trait.
+On the `Cake` entity, to define the relation:
+1. Add a new field `filling` to the `Model`.
+1. Annotate it with `has_many`, and specify the junction table with `via`.
+
+```rust {10,11} title="entity/cake.rs"
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "cake")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ #[sea_orm(has_one)]
+ pub fruit: HasOne,
+ #[sea_orm(has_many, via = "cake_filling")] // M-N relation with junction
+ pub fillings: HasMany,
+}
+```
+
+
+ It's expanded to:
`Relation` in SeaORM is an arrow: it has `from` and `to`. `cake_filling::Relation::Cake` defines `CakeFilling -> Cake`. Calling [`rev`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.RelationDef.html#method.rev) reverses it into `Cake -> CakeFilling`.
@@ -24,53 +46,49 @@ impl Related for Entity {
}
}
```
-
-Similarly, on the `Filling` entity, implement the `Related` trait. First, join with intermediate table `via` the inverse of `cake_filling::Relation::Filling` relation, then join `to` `Cake` entity with `cake_filling::Relation::Cake` relation.
-
-```rust {3,7} title="entity/filling.rs"
-impl Related for Entity {
- fn to() -> RelationDef {
- super::cake_filling::Relation::Cake.def()
- }
-
- fn via() -> Option {
- Some(super::cake_filling::Relation::Filling.def().rev())
- }
+
+
+Similarly, on the `Filling` entity:
+
+```rust {8,9} title="entity/cake.rs"
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "filling")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ #[sea_orm(has_many, via = "cake_filling")]
+ pub cakes: HasMany,
}
```
-## Defining the Inverse Relation
+## Defining the Junction Table
On the `CakeFilling` entity, its `cake_id` attribute is referencing the primary key of `Cake` entity, and its `filling_id` attribute is referencing the primary key of `Filling` entity.
To define the inverse relation:
-1. Add two new variants `Cake` and `Filling` to the `Relation` enum.
-1. Define both relations with `Entity::belongs_to()`.
-
-```rust title="entity/cake_filling.rs"
-#[derive(Copy, Clone, Debug, EnumIter)]
-pub enum Relation {
- Cake,
- Filling,
-}
-
-impl RelationTrait for Relation {
- fn def(&self) -> RelationDef {
- match self {
- Self::Cake => Entity::belongs_to(super::cake::Entity)
- .from(Column::CakeId)
- .to(super::cake::Column::Id)
- .into(),
- Self::Filling => Entity::belongs_to(super::filling::Entity)
- .from(Column::FillingId)
- .to(super::filling::Column::Id)
- .into(),
- }
- }
+1. Add two new fields `cake` and `filling` to the `Model`.
+1. Define both relations with `belongs_to`.
+
+```rust {9-12} title="entity/cake_filling.rs"
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "cake_filling")]
+pub struct Model {
+ #[sea_orm(primary_key, auto_increment = false)]
+ pub cake_id: i32,
+ #[sea_orm(primary_key, auto_increment = false)]
+ pub filling_id: i32,
+ #[sea_orm(belongs_to, from = "cake_id", to = "id")]
+ pub cake: Option,
+ #[sea_orm(belongs_to, from = "filling_id", to = "id")]
+ pub filling: Option,
}
```
-Alternatively, the definition can be shortened by the `DeriveRelation` macro, where the following eliminates the need for the `RelationTrait` implementation above:
+
+ It's expanded to:
```rust
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
@@ -89,46 +107,38 @@ pub enum Relation {
Filling,
}
```
+
-
- Note that the implementation of `Related` with `via` and `to` methods will not be generated if there exists multiple paths via an intermediate table.
+## Limitation of Codegen
-For example, in the schema defined below, there are two paths:
-+ Path 1. `users <-> users_votes <-> bills`
-+ Path 2. `users <-> users_saved_bills <-> bills`
+Usually, the `Related` trait implementations are automatically generated. However, they will not be generated if there exists multiple relations to a related Entity.
-Therefore, the implementation of `Related` will not be generated
+The relation enum variant will still be generated, so they can be used in joins.
-```sql
-CREATE TABLE users
-(
- id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(),
- email TEXT UNIQUE NOT NULL,
- ...
-);
-```
-```sql
-CREATE TABLE bills
-(
- id uuid PRIMARY KEY DEFAULT uuid_generate_v1mc(),
- ...
-);
-```
-```sql
-CREATE TABLE users_votes
-(
- user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE,
- bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE,
- vote boolean NOT NULL,
- CONSTRAINT users_bills_pkey PRIMARY KEY (user_id, bill_id)
-);
-```
-```sql
-CREATE TABLE users_saved_bills
-(
- user_id uuid REFERENCES users (id) ON UPDATE CASCADE ON DELETE CASCADE,
- bill_id uuid REFERENCES bills (id) ON UPDATE CASCADE ON DELETE CASCADE,
- CONSTRAINT users_saved_bills_pkey PRIMARY KEY (user_id, bill_id)
-);
+```rust
+#[sea_orm::model]
+#[derive(DeriveEntityModel, ..)]
+#[sea_orm(table_name = "cake_with_many_fruits")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub fruit_id1: i32,
+ pub fruit_id2: i32,
+ #[sea_orm(belongs_to, relation_enum = "Fruit1", from = "fruit_id1", to = "id")]
+ pub fruit_1: HasOne,
+ #[sea_orm(belongs_to, relation_enum = "Fruit2", from = "fruit_id2", to = "id")]
+ pub fruit_2: HasOne,
+}
+
+// expands to:
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+ #[sea_orm(belongs_to = ..)]
+ Fruit1,
+ #[sea_orm(belongs_to = ..)]
+ Fruit2,
+}
```
-
\ No newline at end of file
+
+The solution is to define relations with the `Linked` which will be described in the next chapter.
diff --git a/SeaORM/docs/06-relation/04-chained-relations.md b/SeaORM/docs/06-relation/04-chained-relations.md
deleted file mode 100644
index cb21665a11d..00000000000
--- a/SeaORM/docs/06-relation/04-chained-relations.md
+++ /dev/null
@@ -1,94 +0,0 @@
-# Chained Relations
-
-The `Related` trait is a representation of the arrows (1-1, 1-N, M-N) we draw on Entity Relationship Diagrams. A [`Linked`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Linked.html) is composed of a chain of relations, and is useful when:
-
-1. there exist multiple join paths between a pair of entities
-1. joining across multiple entities in a relational query
-
-Take [this](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/cake.rs) as a simple example, where we join cake and filling via an intermediate `cake_filling` table.
-
-```rust title="entity/links.rs"
-pub struct CakeToFilling;
-
-impl Linked for CakeToFilling {
- type FromEntity = cake::Entity;
-
- type ToEntity = filling::Entity;
-
- fn link(&self) -> Vec {
- vec![
- cake_filling::Relation::Cake.def().rev(),
- cake_filling::Relation::Filling.def(),
- ]
- }
-}
-```
-
-Alternatively, the `RelationDef` can be defined on the fly, where the following is equivalent to the above:
-
-```rust
-pub struct CakeToFilling;
-
-impl Linked for CakeToFilling {
- type FromEntity = cake::Entity;
-
- type ToEntity = filling::Entity;
-
- fn link(&self) -> Vec {
- vec![
- cake_filling::Relation::Cake.def().rev(),
- cake_filling::Entity::belongs_to(filling::Entity)
- .from(cake_filling::Column::FillingId)
- .to(filling::Column::Id)
- .into(),
- ]
- }
-}
-```
-
-### Lazy Loading
-
-Find fillings that can be filled into a cake with the [`find_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ModelTrait.html#method.find_linked) method.
-
-```rust
-let cake_model = cake::Model {
- id: 12,
- name: "".to_owned(),
-};
-
-assert_eq!(
- cake_model
- .find_linked(cake::CakeToFilling)
- .build(DbBackend::MySql)
- .to_string(),
- [
- "SELECT `filling`.`id`, `filling`.`name`, `filling`.`vendor_id`",
- "FROM `filling`",
- "INNER JOIN `cake_filling` AS `r0` ON `r0`.`filling_id` = `filling`.`id`",
- "INNER JOIN `cake` AS `r1` ON `r1`.`id` = `r0`.`cake_id`",
- "WHERE `r1`.`id` = 12",
- ]
- .join(" ")
-);
-```
-
-### Eager Loading
-
-[`find_also_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_also_linked) is a dual of `find_also_related`; [`find_with_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_with_linked) is a dual of `find_with_related`; :
-
-```rust
-assert_eq!(
- cake::Entity::find()
- .find_also_linked(links::CakeToFilling)
- .build(DbBackend::MySql)
- .to_string(),
- [
- r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#,
- r#"`r1`.`id` AS `B_id`, `r1`.`name` AS `B_name`, `r1`.`vendor_id` AS `B_vendor_id`"#,
- r#"FROM `cake`"#,
- r#"LEFT JOIN `cake_filling` AS `r0` ON `cake`.`id` = `r0`.`cake_id`"#,
- r#"LEFT JOIN `filling` AS `r1` ON `r0`.`filling_id` = `r1`.`id`"#,
- ]
- .join(" ")
-);
-```
\ No newline at end of file
diff --git a/SeaORM/docs/06-relation/04-complex-relations.md b/SeaORM/docs/06-relation/04-complex-relations.md
new file mode 100644
index 00000000000..517bf2e0446
--- /dev/null
+++ b/SeaORM/docs/06-relation/04-complex-relations.md
@@ -0,0 +1,374 @@
+# Complex Relations
+
+## Linked
+
+The `Related` trait is a representation of the arrows (1-1, 1-N, M-N) we draw on Entity Relationship Diagrams. A [`Linked`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Linked.html) is composed of a chain of relations, and is useful when:
+
+1. there exist multiple join paths between a pair of entities, making it impossible to impl `Related`
+1. joining across multiple entities in a relational query
+
+Implementing `Linked` trait is completely optional, as there are other ways of doing relational queries in SeaORM, which will be explained in later chapters.
+With `Linked` implemented, several `find_*_linked` helper methods become available, and relationships can be defined in a single place.
+
+### Defining the Link
+
+Take [this](https://github.com/SeaQL/sea-orm/blob/master/src/tests_cfg/entity_linked.rs) as an example, where we join cake and filling via an intermediate `cake_filling` table.
+
+```rust title="entity/links.rs"
+pub struct CakeToFilling;
+
+impl Linked for CakeToFilling {
+ type FromEntity = cake::Entity;
+ type ToEntity = filling::Entity;
+
+ fn link(&self) -> Vec {
+ vec![
+ cake_filling::Relation::Cake.def().rev(), // cake -> cake_filling
+ cake_filling::Relation::Filling.def(), // cake_filling -> filling
+ ]
+ }
+}
+```
+
+### Lazy Loading
+
+Find fillings that can be filled into a cake with the [`find_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/trait.ModelTrait.html#method.find_linked) method.
+
+```rust
+let cake_model = cake::Model {
+ id: 12,
+ name: "Chocolate".into(),
+};
+
+assert_eq!(
+ cake_model
+ .find_linked(cake::CakeToFilling)
+ .build(DbBackend::MySql)
+ .to_string(),
+ [
+ "SELECT `filling`.`id`, `filling`.`name`, `filling`.`vendor_id`",
+ "FROM `filling`",
+ "INNER JOIN `cake_filling` AS `r0` ON `r0`.`filling_id` = `filling`.`id`",
+ "INNER JOIN `cake` AS `r1` ON `r1`.`id` = `r0`.`cake_id`",
+ "WHERE `r1`.`id` = 12",
+ ]
+ .join(" ")
+);
+```
+
+### Eager Loading
+
+[`find_also_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_also_linked) is a dual of `find_also_related`; [`find_with_linked`](https://docs.rs/sea-orm/*/sea_orm/entity/prelude/struct.Select.html#method.find_with_linked) is a dual of `find_with_related`; :
+
+```rust
+assert_eq!(
+ cake::Entity::find()
+ .find_also_linked(links::CakeToFilling)
+ .build(DbBackend::MySql)
+ .to_string(),
+ [
+ "SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,",
+ "`r1`.`id` AS `B_id`, `r1`.`name` AS `B_name`, `r1`.`vendor_id` AS `B_vendor_id`",
+ "FROM `cake`",
+ "LEFT JOIN `cake_filling` AS `r0` ON `cake`.`id` = `r0`.`cake_id`",
+ "LEFT JOIN `filling` AS `r1` ON `r0`.`filling_id` = `r1`.`id`",
+ ]
+ .join(" ")
+);
+```
+
+## Self Referencing Relations
+
+### Belongs To
+
+```rust title="staff.rs"
+use sea_orm::entity::prelude::*;
+
+#[sea_orm::model]
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "staff")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ pub reports_to_id: Option,
+ #[sea_orm(
+ self_ref,
+ relation_enum = "ReportsTo",
+ relation_reverse = "Manages",
+ from = "reports_to_id",
+ to = "id"
+ )]
+ pub reports_to: HasOne,
+ #[sea_orm(self_ref, relation_enum = "Manages", relation_reverse = "ReportsTo")]
+ pub manages: HasMany,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+```
+
+(or using `compact_model` shim)
+
+```rust title="staff.rs"
+use sea_orm::entity::prelude::*;
+
+#[sea_orm::compact_model]
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "staff")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ pub reports_to_id: Option,
+ #[sea_orm(self_ref, relation_enum = "ReportsTo")]
+ pub reports_to: HasOne,
+ #[sea_orm(self_ref, relation_enum = "Manages")]
+ pub manages: HasMany,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+ #[sea_orm(belongs_to = "Entity", from = "Column::ReportsToId", to = "Column::Id")]
+ ReportsTo,
+ #[sea_orm(has_many = "Entity", via_rel = "Relation::ReportsTo")]
+ Manages,
+}
+
+impl ActiveModelBehavior for ActiveModel {}
+```
+
+#### Entity Loader
+
+```rust
+let staff = staff_compact::Entity::load()
+ .with(staff_compact::Relation::ReportsTo)
+ .with(staff_compact::Relation::Manages)
+ .all(db)
+ .await?;
+
+assert_eq!(staff[0].name, "Alan");
+assert_eq!(staff[0].reports_to, None);
+assert_eq!(staff[0].manages[0].name, "Ben");
+assert_eq!(staff[0].manages[1].name, "Alice");
+
+assert_eq!(staff[1].name, "Ben");
+assert_eq!(staff[1].reports_to.as_ref().unwrap().name, "Alan");
+assert!(staff[1].manages.is_empty());
+
+assert_eq!(staff[2].name, "Alice");
+assert_eq!(staff[1].reports_to.as_ref().unwrap().name, "Alan");
+assert!(staff[2].manages.is_empty());
+
+assert_eq!(staff[3].name, "Elle");
+assert_eq!(staff[3].reports_to, None);
+assert!(staff[3].manages.is_empty());
+```
+
+#### Model Loader
+
+```rust
+let staff = staff::Entity::find()
+ .order_by_asc(staff::Column::Id)
+ .all(db)
+ .await?;
+
+let reports_to = staff
+ .load_self(staff::Entity, staff::Relation::ReportsTo, db)
+ .await?;
+
+assert_eq!(staff[0].name, "Alan");
+assert_eq!(reports_to[0], None);
+
+assert_eq!(staff[1].name, "Ben");
+assert_eq!(reports_to[1].as_ref().unwrap().name, "Alan");
+
+assert_eq!(staff[2].name, "Alice");
+assert_eq!(reports_to[2].as_ref().unwrap().name, "Alan");
+
+assert_eq!(staff[3].name, "Elle");
+assert_eq!(reports_to[3], None);
+```
+
+It can work in reverse too.
+
+```rust
+let manages = staff
+ .load_self_many(staff::Entity, staff::Relation::Manages, db)
+ .await?;
+
+assert_eq!(staff[0].name, "Alan");
+assert_eq!(manages[0].len(), 2);
+assert_eq!(manages[0][0].name, "Ben");
+assert_eq!(manages[0][1].name, "Alice");
+
+assert_eq!(staff[1].name, "Ben");
+assert_eq!(manages[1].len(), 0);
+
+assert_eq!(staff[2].name, "Alice");
+assert_eq!(manages[2].len(), 0);
+
+assert_eq!(staff[3].name, "Elle");
+assert_eq!(manages[3].len(), 0);
+```
+
+### Has Many (M-N)
+
+```rust title="user.rs"
+#[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(self_ref, via = "user_follower", from = "User", to = "Follower")]
+ pub followers: HasMany,
+ #[sea_orm(self_ref, via = "user_follower", reverse)]
+ pub following: HasMany,
+}
+```
+
+```rust title="user_follower.rs"
+#[sea_orm::model]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq)]
+#[sea_orm(table_name = "user_follower")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub user_id: i32,
+ #[sea_orm(primary_key)]
+ pub follower_id: i32,
+ #[sea_orm(belongs_to, from = "user_id", to = "id")]
+ pub user: Option,
+ #[sea_orm(belongs_to, relation_enum = "Follower", from = "follower_id", to = "id")]
+ pub follower: Option,
+}
+```
+
+(or with `compact_model`)
+
+```rust
+#[sea_orm::compact_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(self_ref, via = "user_follower")]
+ pub followers: HasMany,
+ #[sea_orm(self_ref, via = "user_follower", reverse)]
+ pub following: HasMany,
+}
+
+impl RelatedSelfVia for Entity {
+ fn to() -> RelationDef {
+ super::user_follower::Relation::Follower.def()
+ }
+ fn via() -> RelationDef {
+ super::user_follower::Relation::User.def().rev()
+ }
+}
+```
+
+#### Entity Loader
+
+Join paths:
+
+```rust
+user -> profile
+ -> user_follower -> user -> profile
+ -> user_follower (reverse) -> user -> profile
+```
+
+```rust
+let users = user::Entity::load()
+ .with(profile::Entity)
+ .with((user_follower::Entity, profile::Entity))
+ .with((user_follower::Entity::REVERSE, profile::Entity))
+ .all(db)
+ .await?;
+
+assert_eq!(users[1].profile, bob.profile);
+assert_eq!(users[1].followers.len(), 1);
+assert_eq!(users[1].followers[0], sam);
+assert_eq!(users[1].following.len(), 1);
+assert_eq!(users[1].following[0], alice);
+```
+
+#### Model Loader
+
+```rust
+let users = user::Entity::find().all(db).await?;
+let followers = users.load_self_via(user_follower::Entity, db).await?;
+let following = users.load_self_via_rev(user_follower::Entity, db).await?;
+
+assert_eq!(users[1], bob);
+assert_eq!(followers[1], [sam.clone()]);
+assert_eq!(following[1], [alice.clone()]);
+```
+
+## Diamond Relations
+
+Sometimes there exist multiple relations between a pair of entities. Here we take the simplest example, where `Bakery` can have multiple `Worker`.
+
+```rust
+#[sea_orm::model]
+#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
+#[sea_orm(table_name = "bakery")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ pub manager_id: i32,
+ pub cashier_id: i32,
+ #[sea_orm(belongs_to, relation_enum = "Manager", from = "manager_id", to = "id")]
+ pub manager: HasOne,
+ #[sea_orm(belongs_to, relation_enum = "Cashier", from = "cashier_id", to = "id")]
+ pub cashier: HasOne,
+}
+```
+
+How can we define the `Worker` Entity?
+By default, `has_many` invokes the `Related` trait to define the relation.
+As a consequence, we have to specify the `Relation` variant of the related entity manually with the `via_rel` attribute.
+
+```rust title="worker.rs"
+#[sea_orm::model]
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "worker")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+ #[sea_orm(has_many, relation_enum = "BakeryManager", via_rel = "Manager")]
+ pub manager_of: HasMany,
+ #[sea_orm(has_many, relation_enum = "BakeryCashier", via_rel = "Cashier")]
+ pub cashier_of: HasMany,
+}
+```
+
+For compact Entities, it looks like this:
+
+```rust title="worker.rs"
+#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
+#[sea_orm(table_name = "worker")]
+pub struct Model {
+ #[sea_orm(primary_key)]
+ pub id: i32,
+ pub name: String,
+}
+
+#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
+pub enum Relation {
+ #[sea_orm(has_many = "super::bakery::Entity", via_rel = "Relation::Manager")]
+ BakeryManager,
+ #[sea_orm(has_many = "super::bakery::Entity", via_rel = "Relation::Cashier")]
+ BakeryCashier,
+}
+```
+
+Then you can use the relations like:
+
+```rust
+fruit::Entity::find().join(JoinType::LeftJoin, fruit::Relation::ToppingOf.def());
+```
\ No newline at end of file
diff --git a/SeaORM/docs/06-relation/05-self-referencing.md b/SeaORM/docs/06-relation/05-self-referencing.md
deleted file mode 100644
index 6d7ffd78c09..00000000000
--- a/SeaORM/docs/06-relation/05-self-referencing.md
+++ /dev/null
@@ -1,36 +0,0 @@
-# Self Referencing
-
-In previous section, we introduced the [`Linked`](https://docs.rs/sea-orm/*/sea_orm/entity/trait.Linked.html) trait. It can also help you define self referencing relations.
-
-The following example defines an Entity that references itself.
-
-```rust
-use sea_orm::entity::prelude::*;
-
-#[derive(Clone, Debug, PartialEq, Eq, DeriveEntityModel)]
-#[sea_orm(table_name = "self_join")]
-pub struct Model {
- #[sea_orm(primary_key, auto_increment = false)]
- pub uuid: Uuid,
- pub uuid_ref: Option,
- pub time: Option