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'
|
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
|
INSERT INTO `cake` (`id`, `name`) VALUES
|
||||||
(1, 'New York Cheese'),
|
(1, 'New York Cheese'),
|
||||||
(2, 'Chocolate Fudge');
|
(2, 'Chocolate Forest');
|
||||||
|
|
||||||
DROP TABLE IF EXISTS `fruit`;
|
DROP TABLE IF EXISTS `fruit`;
|
||||||
|
|
||||||
@ -20,6 +20,33 @@ CREATE TABLE `fruit` (
|
|||||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||||
|
|
||||||
INSERT INTO `fruit` (`id`, `name`, `cake_id`) VALUES
|
INSERT INTO `fruit` (`id`, `name`, `cake_id`) VALUES
|
||||||
(1, 'Blueberry', 1),
|
(1, 'Blueberry', 1),
|
||||||
(2, 'Rasberry', 1),
|
(2, 'Rasberry', 1),
|
||||||
(3, 'Strawberry', 2);
|
(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 core::marker::PhantomData;
|
||||||
use sea_query::{Iden, IntoIden, JoinType};
|
use sea_query::{Iden, IntoIden, JoinType};
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
use crate::{ColumnTrait, IntoSimpleExpr};
|
use crate::{ColumnTrait, Identity, IntoSimpleExpr, RelationDef};
|
||||||
|
|
||||||
pub use sea_query::JoinType;
|
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;
|
use std::rc::Rc;
|
||||||
|
|
||||||
pub trait QueryHelper: Sized {
|
pub trait QueryHelper: Sized {
|
||||||
@ -138,4 +138,44 @@ pub trait QueryHelper: Sized {
|
|||||||
.order_by_expr(col.into_simple_expr(), Order::Desc);
|
.order_by_expr(col.into_simple_expr(), Order::Desc);
|
||||||
self
|
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::{
|
use crate::{
|
||||||
ColumnTrait, EntityTrait, Identity, Iterable, ModelTrait, PrimaryKeyOfModel, QueryHelper,
|
ColumnTrait, EntityTrait, Iterable, ModelTrait, PrimaryKeyOfModel, QueryHelper, Related,
|
||||||
Related, RelationDef, Select, SelectTwo,
|
Select, SelectTwo,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub use sea_query::JoinType;
|
pub use sea_query::JoinType;
|
||||||
use sea_query::{Expr, IntoIden};
|
|
||||||
use std::rc::Rc;
|
|
||||||
|
|
||||||
impl<E> Select<E>
|
impl<E> Select<E>
|
||||||
where
|
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.
|
/// Left Join with a Related Entity.
|
||||||
pub fn left_join<R>(self, _: R) -> Self
|
pub fn left_join<R>(self, _: R) -> Self
|
||||||
where
|
where
|
||||||
R: EntityTrait,
|
R: EntityTrait,
|
||||||
E: Related<R>,
|
E: Related<R>,
|
||||||
{
|
{
|
||||||
self.join(JoinType::LeftJoin, E::to())
|
self.join_join(JoinType::LeftJoin, E::to(), E::via())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Right Join with a Related Entity.
|
/// Right Join with a Related Entity.
|
||||||
@ -73,7 +38,7 @@ where
|
|||||||
R: EntityTrait,
|
R: EntityTrait,
|
||||||
E: Related<R>,
|
E: Related<R>,
|
||||||
{
|
{
|
||||||
self.join(JoinType::RightJoin, E::to())
|
self.join_join(JoinType::RightJoin, E::to(), E::via())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Inner Join with a Related Entity.
|
/// Inner Join with a Related Entity.
|
||||||
@ -82,7 +47,7 @@ where
|
|||||||
R: EntityTrait,
|
R: EntityTrait,
|
||||||
E: Related<R>,
|
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.
|
/// Join with an Entity Related to me.
|
||||||
@ -105,7 +70,7 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tests_cfg::{cake, fruit};
|
use crate::tests_cfg::{cake, filling, fruit};
|
||||||
use crate::{ColumnTrait, EntityTrait, QueryHelper};
|
use crate::{ColumnTrait, EntityTrait, QueryHelper};
|
||||||
use sea_query::MysqlQueryBuilder;
|
use sea_query::MysqlQueryBuilder;
|
||||||
|
|
||||||
@ -192,4 +157,20 @@ mod tests {
|
|||||||
.join(" ")
|
.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)]
|
#[derive(Copy, Clone, Debug, EnumIter)]
|
||||||
pub enum Relation {
|
pub enum Relation {
|
||||||
Fruit,
|
Fruit,
|
||||||
|
CakeFilling,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EntityTrait for Entity {
|
impl EntityTrait for Entity {
|
||||||
@ -54,6 +55,10 @@ impl RelationTrait for Relation {
|
|||||||
.from(Column::Id)
|
.from(Column::Id)
|
||||||
.to(super::fruit::Column::CakeId)
|
.to(super::fruit::Column::CakeId)
|
||||||
.into(),
|
.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 {
|
impl Model {
|
||||||
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
pub fn find_fruit(&self) -> Select<super::fruit::Entity> {
|
||||||
Entity::find_related().belongs_to::<Entity>(self)
|
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.
|
//! Configurations for test cases and examples. Not intended for actual use.
|
||||||
|
|
||||||
pub mod cake;
|
pub mod cake;
|
||||||
|
pub mod cake_filling;
|
||||||
|
pub mod filling;
|
||||||
pub mod fruit;
|
pub mod fruit;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user