Many to many prototype

This commit is contained in:
Chris Tsang 2021-05-18 00:17:40 +08:00
parent acdee45744
commit 7bf8ab0202
9 changed files with 231 additions and 49 deletions

View File

@ -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.
Avoid macros with DSL, use derive macros where appropriate. Be friendly with IDE tools.

View File

@ -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);
(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);

View File

@ -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;

View File

@ -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<RelationDef>) -> 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
}
}

View File

@ -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<E> Select<E>
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<R>(self, _: R) -> Self
where
R: EntityTrait,
E: Related<R>,
{
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<R>,
{
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<R>,
{
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(" ")
);
}
}

View File

@ -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<super::fruit::Entity> for Entity {
}
}
impl Related<super::filling::Entity> for Entity {
fn to() -> RelationDef {
super::cake_filling::Relation::Filling.def()
}
fn via() -> Option<RelationDef> {
Some(Relation::CakeFilling.def())
}
}
impl Model {
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
Entity::find_related().belongs_to::<Entity>(self)

View File

@ -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(),
}
}
}

52
src/tests_cfg/filling.rs Normal file
View File

@ -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!()
}
}

View File

@ -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;