Many to many prototype
This commit is contained in:
parent
acdee45744
commit
7bf8ab0202
@ -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.
|
@ -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);
|
@ -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;
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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(" ")
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
65
src/tests_cfg/cake_filling.rs
Normal file
65
src/tests_cfg/cake_filling.rs
Normal 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
52
src/tests_cfg/filling.rs
Normal 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!()
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user