From 7bf8ab0202fa5e7a527bbe224ade087f0f2e60bc Mon Sep 17 00:00:00 2001 From: Chris Tsang Date: Tue, 18 May 2021 00:17:40 +0800 Subject: [PATCH] Many to many prototype --- README.md | 2 +- examples/bakery.sql | 35 ++++++++++++++++--- src/entity/relation.rs | 2 +- src/query/helper.rs | 44 ++++++++++++++++++++++-- src/query/join.rs | 63 ++++++++++++--------------------- src/tests_cfg/cake.rs | 15 ++++++++ src/tests_cfg/cake_filling.rs | 65 +++++++++++++++++++++++++++++++++++ src/tests_cfg/filling.rs | 52 ++++++++++++++++++++++++++++ src/tests_cfg/mod.rs | 2 ++ 9 files changed, 231 insertions(+), 49 deletions(-) create mode 100644 src/tests_cfg/cake_filling.rs create mode 100644 src/tests_cfg/filling.rs diff --git a/README.md b/README.md index 1197a4ce..3129fb2a 100644 --- a/README.md +++ b/README.md @@ -49,4 +49,4 @@ Balance between compile-time checking and compilation speed. 3. Avoid 'symbol soup' -Avoid function-like macros with DSL, use derive macros where appropriate. Be friendly with IDE tools. \ No newline at end of file +Avoid macros with DSL, use derive macros where appropriate. Be friendly with IDE tools. \ No newline at end of file diff --git a/examples/bakery.sql b/examples/bakery.sql index 4a811238..e6352db5 100644 --- a/examples/bakery.sql +++ b/examples/bakery.sql @@ -8,7 +8,7 @@ CREATE TABLE `cake` ( INSERT INTO `cake` (`id`, `name`) VALUES (1, 'New York Cheese'), - (2, 'Chocolate Fudge'); + (2, 'Chocolate Forest'); DROP TABLE IF EXISTS `fruit`; @@ -20,6 +20,33 @@ CREATE TABLE `fruit` ( ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; INSERT INTO `fruit` (`id`, `name`, `cake_id`) VALUES - (1, 'Blueberry', 1), - (2, 'Rasberry', 1), - (3, 'Strawberry', 2); \ No newline at end of file + (1, 'Blueberry', 1), + (2, 'Rasberry', 1), + (3, 'Strawberry', 2); + +DROP TABLE IF EXISTS `filling`; + +CREATE TABLE `filling` ( + `id` int NOT NULL AUTO_INCREMENT, + `name` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `filling` (`id`, `name`) VALUES + (1, 'Vanilla'), + (2, 'Lemon'), + (3, 'Mango'); + +DROP TABLE IF EXISTS `cake_filling`; + +CREATE TABLE `cake_filling` ( + `cake_id` int NOT NULL, + `filling_id` int NOT NULL, + PRIMARY KEY (`cake_id`, `filling_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +INSERT INTO `cake_filling` (`cake_id`, `filling_id`) VALUES + (1, 1), + (1, 2), + (2, 2), + (2, 3); \ No newline at end of file diff --git a/src/entity/relation.rs b/src/entity/relation.rs index 0694a88b..9b261fbe 100644 --- a/src/entity/relation.rs +++ b/src/entity/relation.rs @@ -1,4 +1,4 @@ -use crate::{EntityTrait, Identity, IntoIdentity, Select}; +use crate::{EntityTrait, Identity, IntoIdentity, QueryHelper, Select}; use core::marker::PhantomData; use sea_query::{Iden, IntoIden, JoinType}; use std::fmt::Debug; diff --git a/src/query/helper.rs b/src/query/helper.rs index fd7c8cb8..f0845308 100644 --- a/src/query/helper.rs +++ b/src/query/helper.rs @@ -1,7 +1,7 @@ -use crate::{ColumnTrait, IntoSimpleExpr}; +use crate::{ColumnTrait, Identity, IntoSimpleExpr, RelationDef}; pub use sea_query::JoinType; -use sea_query::{Alias, Order, SelectExpr, SelectStatement, SimpleExpr}; +use sea_query::{Alias, Expr, Order, SelectExpr, SelectStatement, SimpleExpr}; use std::rc::Rc; pub trait QueryHelper: Sized { @@ -138,4 +138,44 @@ pub trait QueryHelper: Sized { .order_by_expr(col.into_simple_expr(), Order::Desc); self } + + #[doc(hidden)] + fn join_join(mut self, join: JoinType, rel: RelationDef, via: Option) -> Self { + if let Some(via) = via { + self = self.join(join, via) + } + self.join(join, rel) + } + + /// Join via [`RelationDef`]. + fn join(mut self, join: JoinType, rel: RelationDef) -> Self { + let from_tbl = rel.from_tbl.clone(); + let to_tbl = rel.to_tbl.clone(); + let owner_keys = rel.from_col; + let foreign_keys = rel.to_col; + let condition = match (owner_keys, foreign_keys) { + (Identity::Unary(o1), Identity::Unary(f1)) => { + Expr::tbl(Rc::clone(&from_tbl), o1).equals(Rc::clone(&to_tbl), f1) + } // _ => panic!("Owner key and foreign key mismatch"), + }; + self.query().join(join, Rc::clone(&to_tbl), condition); + self + } + + /// Join via [`RelationDef`] but in reverse direction. + /// Assume when there exist a relation A -> B. + /// You can reverse join B <- A. + fn join_rev(mut self, join: JoinType, rel: RelationDef) -> Self { + let from_tbl = rel.from_tbl.clone(); + let to_tbl = rel.to_tbl.clone(); + let owner_keys = rel.from_col; + let foreign_keys = rel.to_col; + let condition = match (owner_keys, foreign_keys) { + (Identity::Unary(o1), Identity::Unary(f1)) => { + Expr::tbl(Rc::clone(&from_tbl), o1).equals(Rc::clone(&to_tbl), f1) + } // _ => panic!("Owner key and foreign key mismatch"), + }; + self.query().join(join, Rc::clone(&from_tbl), condition); + self + } } diff --git a/src/query/join.rs b/src/query/join.rs index 57b1d377..c9e975dc 100644 --- a/src/query/join.rs +++ b/src/query/join.rs @@ -1,11 +1,8 @@ use crate::{ - ColumnTrait, EntityTrait, Identity, Iterable, ModelTrait, PrimaryKeyOfModel, QueryHelper, - Related, RelationDef, Select, SelectTwo, + ColumnTrait, EntityTrait, Iterable, ModelTrait, PrimaryKeyOfModel, QueryHelper, Related, + Select, SelectTwo, }; - pub use sea_query::JoinType; -use sea_query::{Expr, IntoIden}; -use std::rc::Rc; impl Select where @@ -26,45 +23,13 @@ where } } - /// Join via [`RelationDef`]. - pub fn join(mut self, join: JoinType, rel: RelationDef) -> Self { - let own_tbl = E::default().into_iden(); - let to_tbl = rel.to_tbl.clone(); - let owner_keys = rel.from_col; - let foreign_keys = rel.to_col; - let condition = match (owner_keys, foreign_keys) { - (Identity::Unary(o1), Identity::Unary(f1)) => { - Expr::tbl(Rc::clone(&own_tbl), o1).equals(Rc::clone(&to_tbl), f1) - } // _ => panic!("Owner key and foreign key mismatch"), - }; - self.query.join(join, Rc::clone(&to_tbl), condition); - self - } - - /// Join via [`RelationDef`] but in reverse direction. - /// Assume when there exist a relation A -> B. - /// You can reverse join B <- A. - pub fn join_rev(mut self, join: JoinType, rel: RelationDef) -> Self { - let from_tbl = rel.from_tbl.clone(); - let to_tbl = rel.to_tbl.clone(); - let owner_keys = rel.from_col; - let foreign_keys = rel.to_col; - let condition = match (owner_keys, foreign_keys) { - (Identity::Unary(o1), Identity::Unary(f1)) => { - Expr::tbl(Rc::clone(&from_tbl), o1).equals(Rc::clone(&to_tbl), f1) - } // _ => panic!("Owner key and foreign key mismatch"), - }; - self.query.join(join, Rc::clone(&from_tbl), condition); - self - } - /// Left Join with a Related Entity. pub fn left_join(self, _: R) -> Self where R: EntityTrait, E: Related, { - self.join(JoinType::LeftJoin, E::to()) + self.join_join(JoinType::LeftJoin, E::to(), E::via()) } /// Right Join with a Related Entity. @@ -73,7 +38,7 @@ where R: EntityTrait, E: Related, { - self.join(JoinType::RightJoin, E::to()) + self.join_join(JoinType::RightJoin, E::to(), E::via()) } /// Inner Join with a Related Entity. @@ -82,7 +47,7 @@ where R: EntityTrait, E: Related, { - self.join(JoinType::InnerJoin, E::to()) + self.join_join(JoinType::InnerJoin, E::to(), E::via()) } /// Join with an Entity Related to me. @@ -105,7 +70,7 @@ where #[cfg(test)] mod tests { - use crate::tests_cfg::{cake, fruit}; + use crate::tests_cfg::{cake, filling, fruit}; use crate::{ColumnTrait, EntityTrait, QueryHelper}; use sea_query::MysqlQueryBuilder; @@ -192,4 +157,20 @@ mod tests { .join(" ") ); } + + #[test] + fn join_6() { + assert_eq!( + cake::Entity::find() + .left_join(filling::Entity) + .build(MysqlQueryBuilder) + .to_string(), + [ + "SELECT `cake`.`id`, `cake`.`name` FROM `cake`", + "LEFT JOIN `cake_filling` ON `cake`.`id` = `cake_filling`.`cake_id`", + "LEFT JOIN `filling` ON `cake_filling`.`filling_id` = `filling`.`id`", + ] + .join(" ") + ); + } } diff --git a/src/tests_cfg/cake.rs b/src/tests_cfg/cake.rs index 30f8062b..f3c815f6 100644 --- a/src/tests_cfg/cake.rs +++ b/src/tests_cfg/cake.rs @@ -24,6 +24,7 @@ pub enum PrimaryKey { #[derive(Copy, Clone, Debug, EnumIter)] pub enum Relation { Fruit, + CakeFilling, } impl EntityTrait for Entity { @@ -54,6 +55,10 @@ impl RelationTrait for Relation { .from(Column::Id) .to(super::fruit::Column::CakeId) .into(), + Self::CakeFilling => Entity::has_many(super::cake_filling::Entity) + .from(Column::Id) + .to(super::cake_filling::Column::CakeId) + .into(), } } } @@ -64,6 +69,16 @@ impl Related for Entity { } } +impl Related for Entity { + fn to() -> RelationDef { + super::cake_filling::Relation::Filling.def() + } + + fn via() -> Option { + Some(Relation::CakeFilling.def()) + } +} + impl Model { pub fn find_fruit(&self) -> Select { Entity::find_related().belongs_to::(self) diff --git a/src/tests_cfg/cake_filling.rs b/src/tests_cfg/cake_filling.rs new file mode 100644 index 00000000..14fbdc56 --- /dev/null +++ b/src/tests_cfg/cake_filling.rs @@ -0,0 +1,65 @@ +use crate::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +#[table = "cake_filling"] +pub struct Entity; + +#[derive(Clone, Debug, Default, PartialEq, DeriveModel)] +pub struct Model { + pub cake_id: i32, + pub filling_id: i32, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + CakeId, + FillingId, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + CakeId, + FillingId, +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation { + Cake, + Filling, +} + +impl EntityTrait for Entity { + type Model = Model; + + type Column = Column; + + type PrimaryKey = PrimaryKey; + + type Relation = Relation; +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnType { + match self { + Self::CakeId => ColumnType::Integer(None), + Self::FillingId => ColumnType::Integer(None), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + match self { + Self::Cake => Entity::has_many(super::cake::Entity) + .from(Column::CakeId) + .to(super::cake::Column::Id) + .into(), + Self::Filling => Entity::has_many(super::filling::Entity) + .from(Column::FillingId) + .to(super::filling::Column::Id) + .into(), + } + } +} diff --git a/src/tests_cfg/filling.rs b/src/tests_cfg/filling.rs new file mode 100644 index 00000000..85db6da8 --- /dev/null +++ b/src/tests_cfg/filling.rs @@ -0,0 +1,52 @@ +use crate::entity::prelude::*; + +#[derive(Copy, Clone, Default, Debug, DeriveEntity)] +#[table = "filling"] +pub struct Entity; + +#[derive(Clone, Debug, Default, PartialEq, DeriveModel)] +pub struct Model { + pub id: i32, + pub name: String, +} + +#[derive(Copy, Clone, Debug, EnumIter, DeriveColumn)] +pub enum Column { + Id, + Name, +} + +#[derive(Copy, Clone, Debug, EnumIter, DerivePrimaryKey)] +pub enum PrimaryKey { + Id, +} + +#[derive(Copy, Clone, Debug, EnumIter)] +pub enum Relation {} + +impl EntityTrait for Entity { + type Model = Model; + + type Column = Column; + + type PrimaryKey = PrimaryKey; + + type Relation = Relation; +} + +impl ColumnTrait for Column { + type EntityName = Entity; + + fn def(&self) -> ColumnType { + match self { + Self::Id => ColumnType::Integer(None), + Self::Name => ColumnType::String(None), + } + } +} + +impl RelationTrait for Relation { + fn def(&self) -> RelationDef { + panic!() + } +} diff --git a/src/tests_cfg/mod.rs b/src/tests_cfg/mod.rs index 73f3b730..e39a16f3 100644 --- a/src/tests_cfg/mod.rs +++ b/src/tests_cfg/mod.rs @@ -1,4 +1,6 @@ //! Configurations for test cases and examples. Not intended for actual use. pub mod cake; +pub mod cake_filling; +pub mod filling; pub mod fruit;