Merge branch 'master' into rename-col-name-n-variant

This commit is contained in:
Billy Chan 2021-09-30 10:48:43 +08:00
commit 54150382b9
No known key found for this signature in database
GPG Key ID: A2D690CAC7DF3CC7
17 changed files with 251 additions and 69 deletions

View File

@ -1,5 +1,7 @@
# Architecture # Architecture
> Let's dive under the Sea 🤿
To understand the architecture of SeaORM, let's discuss what is an ORM. ORM exists to provide abstractions over common operations you would do against a database and hide the implementation details like the actual SQL queries. To understand the architecture of SeaORM, let's discuss what is an ORM. ORM exists to provide abstractions over common operations you would do against a database and hide the implementation details like the actual SQL queries.
With a good ORM, you shouldn't bother to look under the API surface. Until you do. I hear you say *'abstraction leaks'*, and yes, it does. With a good ORM, you shouldn't bother to look under the API surface. Until you do. I hear you say *'abstraction leaks'*, and yes, it does.

View File

@ -239,7 +239,7 @@ fruit::Entity::delete_many()
1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md) 1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md)
1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md) 1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md)
1. [Compare with Diesel](https://www.sea-ql.org/SeaORM/docs/internal-design/diesel) 1. [Change Log](https://github.com/SeaQL/sea-orm/tree/master/CHANGELOG.md)
## License ## License

View File

@ -293,7 +293,7 @@ pub trait EntityTrait: EntityName {
/// assert_eq!( /// assert_eq!(
/// db.into_transaction_log(), /// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values( /// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1)"#, vec!["Apple Pie".into()] /// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1) RETURNING "id""#, vec!["Apple Pie".into()]
/// )]); /// )]);
/// ``` /// ```
fn insert<A>(model: A) -> Insert<A> fn insert<A>(model: A) -> Insert<A>
@ -344,7 +344,7 @@ pub trait EntityTrait: EntityName {
/// assert_eq!( /// assert_eq!(
/// db.into_transaction_log(), /// db.into_transaction_log(),
/// vec![Transaction::from_sql_and_values( /// vec![Transaction::from_sql_and_values(
/// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1), ($2)"#, /// DbBackend::Postgres, r#"INSERT INTO "cake" ("name") VALUES ($1), ($2) RETURNING "id""#,
/// vec!["Apple Pie".into(), "Orange Scone".into()] /// vec!["Apple Pie".into(), "Orange Scone".into()]
/// )]); /// )]);
/// ``` /// ```

View File

@ -1,5 +1,7 @@
use crate::{EntityTrait, QuerySelect, RelationDef, Select}; use crate::{
use sea_query::JoinType; join_tbl_on_condition, unpack_table_ref, EntityTrait, QuerySelect, RelationDef, Select,
};
use sea_query::{Alias, IntoIden, JoinType, SeaRc};
pub type LinkDef = RelationDef; pub type LinkDef = RelationDef;
@ -12,8 +14,20 @@ pub trait Linked {
fn find_linked(&self) -> Select<Self::ToEntity> { fn find_linked(&self) -> Select<Self::ToEntity> {
let mut select = Select::new(); let mut select = Select::new();
for rel in self.link().into_iter().rev() { for (i, rel) in self.link().into_iter().rev().enumerate() {
select = select.join_rev(JoinType::InnerJoin, rel); let from_tbl = Alias::new(&format!("r{}", i)).into_iden();
let to_tbl = if i > 0 {
Alias::new(&format!("r{}", i - 1)).into_iden()
} else {
unpack_table_ref(&rel.to_tbl)
};
select.query().join_as(
JoinType::InnerJoin,
unpack_table_ref(&rel.from_tbl),
SeaRc::clone(&from_tbl),
join_tbl_on_condition(from_tbl, to_tbl, rel.from_col, rel.to_col),
);
} }
select select
} }

View File

@ -24,7 +24,8 @@ pub trait ModelTrait: Clone + Send + Debug {
where where
L: Linked<FromEntity = Self::Entity>, L: Linked<FromEntity = Self::Entity>,
{ {
l.find_linked().belongs_to(self) let tbl_alias = &format!("r{}", l.link().len() - 1);
l.find_linked().belongs_to_tbl_alias(self, tbl_alias)
} }
} }

View File

@ -1,5 +1,5 @@
use crate::{ use crate::{
error::*, ActiveModelTrait, DatabaseConnection, EntityTrait, Insert, PrimaryKeyTrait, error::*, ActiveModelTrait, DatabaseConnection, DbBackend, EntityTrait, Insert, PrimaryKeyTrait,
Statement, TryFromU64, Statement, TryFromU64,
}; };
use sea_query::InsertStatement; use sea_query::InsertStatement;
@ -37,8 +37,7 @@ where
// TODO: extract primary key's value from query // TODO: extract primary key's value from query
// so that self is dropped before entering await // so that self is dropped before entering await
let mut query = self.query; let mut query = self.query;
#[cfg(feature = "sqlx-postgres")] if db.get_database_backend() == DbBackend::Postgres {
if let DatabaseConnection::SqlxPostgresPoolConnection(_) = db {
use crate::{sea_query::Query, Iterable}; use crate::{sea_query::Query, Iterable};
if <A::Entity as EntityTrait>::PrimaryKey::iter().count() > 0 { if <A::Entity as EntityTrait>::PrimaryKey::iter().count() > 0 {
query.returning( query.returning(
@ -86,14 +85,13 @@ where
{ {
type PrimaryKey<A> = <<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey; type PrimaryKey<A> = <<A as ActiveModelTrait>::Entity as EntityTrait>::PrimaryKey;
type ValueTypeOf<A> = <PrimaryKey<A> as PrimaryKeyTrait>::ValueType; type ValueTypeOf<A> = <PrimaryKey<A> as PrimaryKeyTrait>::ValueType;
let last_insert_id = match db { let last_insert_id = match db.get_database_backend() {
#[cfg(feature = "sqlx-postgres")] DbBackend::Postgres => {
DatabaseConnection::SqlxPostgresPoolConnection(conn) => {
use crate::{sea_query::Iden, Iterable}; use crate::{sea_query::Iden, Iterable};
let cols = PrimaryKey::<A>::iter() let cols = PrimaryKey::<A>::iter()
.map(|col| col.to_string()) .map(|col| col.to_string())
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let res = conn.query_one(statement).await?.unwrap(); let res = db.query_one(statement).await?.unwrap();
res.try_get_many("", cols.as_ref()).unwrap_or_default() res.try_get_many("", cols.as_ref()).unwrap_or_default()
} }
_ => { _ => {

View File

@ -1,4 +1,4 @@
use crate::{error::*, DatabaseConnection, SelectorTrait}; use crate::{error::*, DatabaseConnection, DbBackend, SelectorTrait};
use async_stream::stream; use async_stream::stream;
use futures::Stream; use futures::Stream;
use sea_query::{Alias, Expr, SelectStatement}; use sea_query::{Alias, Expr, SelectStatement};
@ -63,11 +63,8 @@ where
Some(res) => res, Some(res) => res,
None => return Ok(0), None => return Ok(0),
}; };
let num_items = match self.db { let num_items = match builder {
#[cfg(feature = "sqlx-postgres")] DbBackend::Postgres => result.try_get::<i64>("", "num_items")? as usize,
DatabaseConnection::SqlxPostgresPoolConnection(_) => {
result.try_get::<i64>("", "num_items")? as usize
}
_ => result.try_get::<i32>("", "num_items")? as usize, _ => result.try_get::<i32>("", "num_items")? as usize,
}; };
Ok(num_items) Ok(num_items)
@ -192,7 +189,7 @@ mod tests {
(db, vec![page1, page2, page3]) (db, vec![page1, page2, page3])
} }
fn setup_num_items() -> (DatabaseConnection, i32) { fn setup_num_items() -> (DatabaseConnection, i64) {
let num_items = 3; let num_items = 3;
let db = MockDatabase::new(DbBackend::Postgres) let db = MockDatabase::new(DbBackend::Postgres)
.append_query_results(vec![vec![maplit::btreemap! { .append_query_results(vec![vec![maplit::btreemap! {

View File

@ -335,7 +335,7 @@
//! //!
//! 1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md) //! 1. [Design](https://github.com/SeaQL/sea-orm/tree/master/DESIGN.md)
//! 1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md) //! 1. [Architecture](https://github.com/SeaQL/sea-orm/tree/master/ARCHITECTURE.md)
//! 1. [Compare with Diesel](https://www.sea-ql.org/SeaORM/docs/internal-design/diesel) //! 1. [Change Log](https://github.com/SeaQL/sea-orm/tree/master/CHANGELOG.md)
//! //!
//! ## License //! ## License
//! //!

View File

@ -2,10 +2,11 @@ use crate::{
ColumnTrait, EntityTrait, Identity, IntoIdentity, IntoSimpleExpr, Iterable, ModelTrait, ColumnTrait, EntityTrait, Identity, IntoIdentity, IntoSimpleExpr, Iterable, ModelTrait,
PrimaryKeyToColumn, RelationDef, PrimaryKeyToColumn, RelationDef,
}; };
pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement};
use sea_query::{ use sea_query::{
Expr, IntoCondition, LockType, SeaRc, SelectExpr, SelectStatement, SimpleExpr, TableRef, Alias, Expr, Iden, IntoCondition, LockType, SeaRc, SelectExpr, SelectStatement, SimpleExpr,
TableRef,
}; };
pub use sea_query::{Condition, ConditionalStatement, DynIden, JoinType, Order, OrderedStatement};
// LINT: when the column does not appear in tables selected from // LINT: when the column does not appear in tables selected from
// LINT: when there is a group by clause, but some columns don't have aggregate functions // LINT: when there is a group by clause, but some columns don't have aggregate functions
@ -268,6 +269,48 @@ pub trait QueryFilter: Sized {
/// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 4 OR `cake`.`id` = 5" /// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`id` = 4 OR `cake`.`id` = 5"
/// ); /// );
/// ``` /// ```
///
/// Add a runtime-built condition tree.
/// ```
/// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend};
/// struct Input {
/// name: Option<String>,
/// }
/// let input = Input { name: Some("cheese".to_owned()) };
///
/// let mut conditions = Condition::all();
/// if let Some(name) = input.name {
/// conditions = conditions.add(cake::Column::Name.contains(&name));
/// }
///
/// assert_eq!(
/// cake::Entity::find()
/// .filter(conditions)
/// .build(DbBackend::MySql)
/// .to_string(),
/// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%cheese%'"
/// );
/// ```
///
/// Add a runtime-built condition tree, functional-way.
/// ```
/// use sea_orm::{entity::*, query::*, tests_cfg::cake, DbBackend};
/// struct Input {
/// name: Option<String>,
/// }
/// let input = Input { name: Some("cheese".to_owned()) };
///
/// assert_eq!(
/// cake::Entity::find()
/// .filter(
/// Condition::all()
/// .add_option(input.name.map(|n| cake::Column::Name.contains(&n)))
/// )
/// .build(DbBackend::MySql)
/// .to_string(),
/// "SELECT `cake`.`id`, `cake`.`name` FROM `cake` WHERE `cake`.`name` LIKE '%cheese%'"
/// );
/// ```
fn filter<F>(mut self, filter: F) -> Self fn filter<F>(mut self, filter: F) -> Self
where where
F: IntoCondition, F: IntoCondition,
@ -287,14 +330,35 @@ pub trait QueryFilter: Sized {
} }
self self
} }
fn belongs_to_tbl_alias<M>(mut self, model: &M, tbl_alias: &str) -> Self
where
M: ModelTrait,
{
for key in <M::Entity as EntityTrait>::PrimaryKey::iter() {
let col = key.into_column();
let expr = Expr::tbl(Alias::new(tbl_alias), col).eq(model.get(col));
self = self.filter(expr);
}
self
}
} }
fn join_condition(rel: RelationDef) -> SimpleExpr { pub(crate) fn join_condition(rel: RelationDef) -> SimpleExpr {
let from_tbl = unpack_table_ref(&rel.from_tbl); let from_tbl = unpack_table_ref(&rel.from_tbl);
let to_tbl = unpack_table_ref(&rel.to_tbl); let to_tbl = unpack_table_ref(&rel.to_tbl);
let owner_keys = rel.from_col; let owner_keys = rel.from_col;
let foreign_keys = rel.to_col; let foreign_keys = rel.to_col;
join_tbl_on_condition(from_tbl, to_tbl, owner_keys, foreign_keys)
}
pub(crate) fn join_tbl_on_condition(
from_tbl: SeaRc<dyn Iden>,
to_tbl: SeaRc<dyn Iden>,
owner_keys: Identity,
foreign_keys: Identity,
) -> SimpleExpr {
match (owner_keys, foreign_keys) { match (owner_keys, foreign_keys) {
(Identity::Unary(o1), Identity::Unary(f1)) => { (Identity::Unary(o1), Identity::Unary(f1)) => {
Expr::tbl(SeaRc::clone(&from_tbl), o1).equals(SeaRc::clone(&to_tbl), f1) Expr::tbl(SeaRc::clone(&from_tbl), o1).equals(SeaRc::clone(&to_tbl), f1)

View File

@ -74,8 +74,9 @@ where
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::tests_cfg::{cake, cake_filling, cake_filling_price, filling, fruit}; use crate::tests_cfg::{cake, cake_filling, cake_filling_price, entity_linked, filling, fruit};
use crate::{ColumnTrait, DbBackend, EntityTrait, ModelTrait, QueryFilter, QueryTrait}; use crate::{ColumnTrait, DbBackend, EntityTrait, ModelTrait, QueryFilter, QueryTrait};
use pretty_assertions::assert_eq;
#[test] #[test]
fn join_1() { fn join_1() {
@ -188,7 +189,7 @@ mod tests {
assert_eq!( assert_eq!(
find_filling.build(DbBackend::MySql).to_string(), find_filling.build(DbBackend::MySql).to_string(),
[ [
"SELECT `filling`.`id`, `filling`.`name` FROM `filling`", "SELECT `filling`.`id`, `filling`.`name`, `filling`.`vendor_id` FROM `filling`",
"INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id`", "INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id`",
"INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id`", "INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id`",
] ]
@ -243,15 +244,15 @@ mod tests {
assert_eq!( assert_eq!(
cake_model cake_model
.find_linked(cake::CakeToFilling) .find_linked(entity_linked::CakeToFilling)
.build(DbBackend::MySql) .build(DbBackend::MySql)
.to_string(), .to_string(),
[ [
r#"SELECT `filling`.`id`, `filling`.`name`"#, r#"SELECT `filling`.`id`, `filling`.`name`, `filling`.`vendor_id`"#,
r#"FROM `filling`"#, r#"FROM `filling`"#,
r#"INNER JOIN `cake_filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, r#"INNER JOIN `cake_filling` AS `r0` ON `r0`.`filling_id` = `filling`.`id`"#,
r#"INNER JOIN `cake` ON `cake`.`id` = `cake_filling`.`cake_id`"#, r#"INNER JOIN `cake` AS `r1` ON `r1`.`id` = `r0`.`cake_id`"#,
r#"WHERE `cake`.`id` = 12"#, r#"WHERE `r1`.`id` = 12"#,
] ]
.join(" ") .join(" ")
); );
@ -259,14 +260,38 @@ mod tests {
#[test] #[test]
fn join_11() { fn join_11() {
let cake_model = cake::Model {
id: 18,
name: "".to_owned(),
};
assert_eq!(
cake_model
.find_linked(entity_linked::CakeToFillingVendor)
.build(DbBackend::MySql)
.to_string(),
[
r#"SELECT `vendor`.`id`, `vendor`.`name`"#,
r#"FROM `vendor`"#,
r#"INNER JOIN `filling` AS `r0` ON `r0`.`vendor_id` = `vendor`.`id`"#,
r#"INNER JOIN `cake_filling` AS `r1` ON `r1`.`filling_id` = `r0`.`id`"#,
r#"INNER JOIN `cake` AS `r2` ON `r2`.`id` = `r1`.`cake_id`"#,
r#"WHERE `r2`.`id` = 18"#,
]
.join(" ")
);
}
#[test]
fn join_12() {
assert_eq!( assert_eq!(
cake::Entity::find() cake::Entity::find()
.find_also_linked(cake::CakeToFilling) .find_also_linked(entity_linked::CakeToFilling)
.build(DbBackend::MySql) .build(DbBackend::MySql)
.to_string(), .to_string(),
[ [
r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#, r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#,
r#"`filling`.`id` AS `B_id`, `filling`.`name` AS `B_name`"#, r#"`filling`.`id` AS `B_id`, `filling`.`name` AS `B_name`, `filling`.`vendor_id` AS `B_vendor_id`"#,
r#"FROM `cake`"#, r#"FROM `cake`"#,
r#"LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#, r#"LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#,
r#"LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#, r#"LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#,
@ -274,4 +299,23 @@ mod tests {
.join(" ") .join(" ")
); );
} }
#[test]
fn join_13() {
assert_eq!(
cake::Entity::find()
.find_also_linked(entity_linked::CakeToFillingVendor)
.build(DbBackend::MySql)
.to_string(),
[
r#"SELECT `cake`.`id` AS `A_id`, `cake`.`name` AS `A_name`,"#,
r#"`vendor`.`id` AS `B_id`, `vendor`.`name` AS `B_name`"#,
r#"FROM `cake`"#,
r#"LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`"#,
r#"LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`"#,
r#"LEFT JOIN `vendor` ON `filling`.`vendor_id` = `vendor`.`id`"#,
]
.join(" ")
);
}
} }

View File

@ -32,20 +32,4 @@ impl Related<super::filling::Entity> for Entity {
} }
} }
#[derive(Debug)]
pub struct CakeToFilling;
impl Linked for CakeToFilling {
type FromEntity = Entity;
type ToEntity = super::filling::Entity;
fn link(&self) -> Vec<RelationDef> {
vec![
super::cake_filling::Relation::Cake.def().rev(),
super::cake_filling::Relation::Filling.def(),
]
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

View File

@ -75,20 +75,4 @@ impl Related<super::filling::Entity> for Entity {
} }
} }
#[derive(Debug)]
pub struct CakeToFilling;
impl Linked for CakeToFilling {
type FromEntity = Entity;
type ToEntity = super::filling::Entity;
fn link(&self) -> Vec<RelationDef> {
vec![
super::cake_filling::Relation::Cake.def().rev(),
super::cake_filling::Relation::Filling.def(),
]
}
}
impl ActiveModelBehavior for ActiveModel {} impl ActiveModelBehavior for ActiveModel {}

View File

@ -0,0 +1,34 @@
use crate::entity::prelude::*;
#[derive(Debug)]
pub struct CakeToFilling;
impl Linked for CakeToFilling {
type FromEntity = super::cake::Entity;
type ToEntity = super::filling::Entity;
fn link(&self) -> Vec<RelationDef> {
vec![
super::cake_filling::Relation::Cake.def().rev(),
super::cake_filling::Relation::Filling.def(),
]
}
}
#[derive(Debug)]
pub struct CakeToFillingVendor;
impl Linked for CakeToFillingVendor {
type FromEntity = super::cake::Entity;
type ToEntity = super::vendor::Entity;
fn link(&self) -> Vec<RelationDef> {
vec![
super::cake_filling::Relation::Cake.def().rev(),
super::cake_filling::Relation::Filling.def(),
super::filling::Relation::Vendor.def(),
]
}
}

View File

@ -9,6 +9,7 @@ pub struct Entity;
pub struct Model { pub struct Model {
pub id: i32, pub id: i32,
pub name: String, pub name: String,
pub vendor_id: Option<i32>,
#[sea_orm(ignore)] #[sea_orm(ignore)]
pub ignored_attr: i32, pub ignored_attr: i32,
} }
@ -18,6 +19,7 @@ pub struct Model {
pub enum Column { pub enum Column {
Id, Id,
Name, Name,
VendorId,
} }
// Then, customize each column names here. // Then, customize each column names here.
@ -46,7 +48,9 @@ impl PrimaryKeyTrait for PrimaryKey {
} }
#[derive(Copy, Clone, Debug, EnumIter)] #[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {} pub enum Relation {
Vendor,
}
impl ColumnTrait for Column { impl ColumnTrait for Column {
type EntityName = Entity; type EntityName = Entity;
@ -55,13 +59,19 @@ impl ColumnTrait for Column {
match self { match self {
Self::Id => ColumnType::Integer.def(), Self::Id => ColumnType::Integer.def(),
Self::Name => ColumnType::String(None).def(), Self::Name => ColumnType::String(None).def(),
Self::VendorId => ColumnType::Integer.def().nullable(),
} }
} }
} }
impl RelationTrait for Relation { impl RelationTrait for Relation {
fn def(&self) -> RelationDef { fn def(&self) -> RelationDef {
panic!() match self {
Self::Vendor => Entity::belongs_to(super::vendor::Entity)
.from(Column::VendorId)
.to(super::vendor::Column::Id)
.into(),
}
} }
} }

View File

@ -4,8 +4,10 @@ pub mod cake;
pub mod cake_expanded; pub mod cake_expanded;
pub mod cake_filling; pub mod cake_filling;
pub mod cake_filling_price; pub mod cake_filling_price;
pub mod entity_linked;
pub mod filling; pub mod filling;
pub mod fruit; pub mod fruit;
pub mod vendor;
pub use cake::Entity as Cake; pub use cake::Entity as Cake;
pub use cake_expanded::Entity as CakeExpanded; pub use cake_expanded::Entity as CakeExpanded;
@ -13,3 +15,4 @@ pub use cake_filling::Entity as CakeFilling;
pub use cake_filling_price::Entity as CakeFillingPrice; pub use cake_filling_price::Entity as CakeFillingPrice;
pub use filling::Entity as Filling; pub use filling::Entity as Filling;
pub use fruit::Entity as Fruit; pub use fruit::Entity as Fruit;
pub use vendor::Entity as Vendor;

27
src/tests_cfg/vendor.rs Normal file
View File

@ -0,0 +1,27 @@
use crate as sea_orm;
use crate::entity::prelude::*;
#[derive(Clone, Debug, PartialEq, DeriveEntityModel)]
#[sea_orm(table_name = "vendor")]
pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
}
#[derive(Copy, Clone, Debug, EnumIter)]
pub enum Relation {}
impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!()
}
}
impl Related<super::filling::Entity> for Entity {
fn to() -> RelationDef {
super::filling::Relation::Vendor.def()
}
}
impl ActiveModelBehavior for ActiveModel {}

View File

@ -708,6 +708,26 @@ pub async fn linked() -> Result<(), DbErr> {
] ]
); );
let baker_bob = Baker::find()
.filter(baker::Column::Id.eq(1))
.one(&ctx.db)
.await?
.unwrap();
let baker_bob_customers = baker_bob
.find_linked(baker::BakedForCustomer)
.all(&ctx.db)
.await?;
assert_eq!(
baker_bob_customers,
vec![customer::Model {
id: 2,
name: "Kara".to_owned(),
notes: Some("Loves all cakes".to_owned()),
}]
);
ctx.delete().await; ctx.delete().await;
Ok(()) Ok(())