Skip to content

Commit

Permalink
Fix SelfRef relations, add reverse SelfRef (#99)
Browse files Browse the repository at this point in the history
## PR Info
- Closes #94

## New Features

- [X] Relations derive produces the reverse of a SelfRef relation

## Bug Fixes

- [X] Relations derive could not handle properly SelfRef relations
  • Loading branch information
karatakis authored Nov 14, 2022
1 parent 98eb651 commit d7f1626
Show file tree
Hide file tree
Showing 22 changed files with 340 additions and 126 deletions.
106 changes: 67 additions & 39 deletions derive/src/relation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ pub struct SeaOrm {
}

pub fn compact_relation_fn(item: &syn::DataEnum) -> Result<TokenStream, crate::error::Error> {
let (loaders, functions): (Vec<_>, Vec<_>) = item
let relations_parameters: Vec<(String, Option<String>, Option<String>, bool)> = item
.variants
.iter()
.map(
|variant| -> Result<(TokenStream, TokenStream), crate::error::Error> {
|variant| -> Result<(String, Option<String>, Option<String>, bool), crate::error::Error> {
let attrs = SeaOrm::from_attributes(&variant.attrs)?;

let belongs_to = match attrs.belongs_to {
Expand All @@ -30,22 +30,12 @@ pub fn compact_relation_fn(item: &syn::DataEnum) -> Result<TokenStream, crate::e
_ => None,
};

relation_fn(variant.ident.to_string(), belongs_to, has_many)
Ok((variant.ident.to_string(), belongs_to, has_many, false))
},
)
.collect::<Result<Vec<_>, crate::error::Error>>()?
.into_iter()
.map(|(loader, func)| (loader, func))
.unzip();
.collect::<Result<Vec<_>, crate::error::Error>>()?;

Ok(quote! {
#(#loaders)*

#[async_graphql::ComplexObject]
impl Model {
#(#functions)*
}
})
produce_relations(relations_parameters)
}

#[derive(Debug)]
Expand Down Expand Up @@ -122,23 +112,50 @@ pub fn expanded_relation_fn(item: &syn::ItemImpl) -> Result<TokenStream, crate::
})
.collect::<Result<Vec<ExpandedParams>, crate::error::Error>>()?;

let (loaders, functions): (Vec<_>, Vec<_>) = expanded_params
let relations_parameters: Vec<(String, Option<String>, Option<String>, bool)> = expanded_params
.iter()
.map(
|params| -> Result<(TokenStream, TokenStream), crate::error::Error> {
let belongs_to = if params.relation_type.to_string().eq("belongs_to") {
Some(params.related_type.to_token_stream().to_string())
} else {
None
};
.map(|params| -> Result<(String, Option<String>, Option<String>, bool), crate::error::Error> {
let belongs_to = if params.relation_type.to_string().eq("belongs_to") {
Some(params.related_type.to_token_stream().to_string())
} else {
None
};

let has_many = if params.relation_type.to_string().ne("belongs_to") {
Some(params.related_type.to_token_stream().to_string())
} else {
None
};

let relation_name = params.variant.to_string();

Ok((relation_name, belongs_to, has_many, false))
})
.collect::<Result<Vec<_>, crate::error::Error>>()?;

let has_many = if params.relation_type.to_string().ne("belongs_to") {
Some(params.related_type.to_token_stream().to_string())
} else {
None
};
produce_relations(relations_parameters)
}

relation_fn(params.variant.to_string(), belongs_to, has_many)
pub fn produce_relations(
relations_parameters: Vec<(String, Option<String>, Option<String>, bool)>,
) -> Result<TokenStream, crate::error::Error> {
let relations_copy = relations_parameters.clone();

let reverse_self_references_parameters = relations_copy
.into_iter()
.filter(|(_, belongs_to, has_one, _)| {
belongs_to.eq(&Some("Entity".into())) || has_one.eq(&Some("Entity".into()))
})
.map(|(relation_name, belongs_to, has_many, _)| {
(relation_name, has_many, belongs_to, true)
});

let (loaders, functions): (Vec<_>, Vec<_>) = relations_parameters
.into_iter()
.chain(reverse_self_references_parameters)
.map(
|(relation_name, belongs_to, has_many, reverse)| -> Result<(TokenStream, TokenStream), crate::error::Error> {
relation_fn(relation_name, belongs_to, has_many, reverse)
},
)
.collect::<Result<Vec<_>, crate::error::Error>>()?
Expand All @@ -147,8 +164,6 @@ pub fn expanded_relation_fn(item: &syn::ItemImpl) -> Result<TokenStream, crate::
.unzip();

Ok(quote! {
#item

#(#loaders)*

#[async_graphql::ComplexObject]
Expand All @@ -162,9 +177,22 @@ pub fn relation_fn(
relation_name: String,
belongs_to: Option<String>,
has_many: Option<String>,
reverse: bool,
) -> Result<(TokenStream, TokenStream), crate::error::Error> {
let relation_ident = format_ident!("{}", relation_name.to_upper_camel_case());

let relation_name = if reverse {
format_ident!("{}Reverse", relation_name.to_upper_camel_case())
} else {
format_ident!("{}", relation_name.to_upper_camel_case())
};

let (reverse, column_type) = if reverse {
(quote! { true }, quote! { to_col })
} else {
(quote! { false }, quote! { from_col })
};

let target_path = if let Some(target_path) = &has_many {
target_path
} else if let Some(target_path) = &belongs_to {
Expand All @@ -180,9 +208,7 @@ pub fn relation_fn(
.parse()
.unwrap()
} else {
return Err(crate::error::Error::Internal(
"Cannot parse entity path".into(),
));
quote! { self }
};

let (return_type, extra_imports, map_method) = if has_many.is_some() {
Expand All @@ -200,7 +226,7 @@ pub fn relation_fn(
};

let relation_enum = quote! {Relation::#relation_ident};
let foreign_key_name = format_ident!("{}FK", relation_ident).to_token_stream();
let foreign_key_name = format_ident!("{}FK", relation_name).to_token_stream();

Ok((
quote! {
Expand Down Expand Up @@ -230,6 +256,7 @@ pub fn relation_fn(
::fetch_relation_data::<#path::Entity, #path::Filter, #path::OrderBy>(
keys,
#relation_enum.def(),
#reverse,
&self.db,
).await?
.into_iter()
Expand All @@ -242,7 +269,7 @@ pub fn relation_fn(
}
},
quote! {
pub async fn #relation_ident<'a>(
pub async fn #relation_name<'a>(
&self,
ctx: &async_graphql::Context<'a>,
) -> Option<#return_type> {
Expand All @@ -253,16 +280,17 @@ pub fn relation_fn(
.data::<async_graphql::dataloader::DataLoader<crate::OrmDataloader>>()
.unwrap();

let from_column: Column = Column::from_str(
// TODO support multiple value keys
let target_column: Column = Column::from_str(
#relation_enum
.def()
.from_col
.#column_type
.to_string()
.to_snake_case()
.as_str()
).unwrap();

let key = #foreign_key_name(seaography::RelationKeyStruct(self.get(from_column), None, None));
let key = #foreign_key_name(seaography::RelationKeyStruct(self.get(target_column), None, None));

let data: Option<_> = data_loader.load_one(key).await.unwrap();

Expand Down
6 changes: 3 additions & 3 deletions examples/mysql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ name = 'seaography-mysql-example'
version = '0.2.0'

[dependencies]
async-graphql = { version = "4.0.10", features = ["decimal", "chrono", "dataloader"] }
async-graphql-poem = { version = "4.0.10" }
poem = { version = "1.3.29" }
async-graphql = { version = "4.0.14", features = ["decimal", "chrono", "dataloader"] }
async-graphql-poem = { version = "4.0.14" }
async-trait = { version = "0.1.53" }
dotenv = "0.15.0"
poem = { version = "1.3.29" }
sea-orm = { version = "^0.10", features = ["sqlx-mysql", "runtime-async-std-native-tls"] }
tokio = { version = "1.17.0", features = ["macros", "rt-multi-thread"] }
tracing = { version = "0.1.34" }
Expand Down
5 changes: 3 additions & 2 deletions examples/mysql/sakila-data.sql

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions examples/mysql/sakila-schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,9 @@ CREATE TABLE film_category (

--
-- Table structure for table `film_text`
--
--
-- InnoDB added FULLTEXT support in 5.6.10. If you use an
-- earlier version, then consider upgrading (recommended) or
-- earlier version, then consider upgrading (recommended) or
-- changing InnoDB to MyISAM as the film_text engine
--

Expand Down Expand Up @@ -315,6 +315,7 @@ CREATE TABLE staff (
first_name VARCHAR(45) NOT NULL,
last_name VARCHAR(45) NOT NULL,
address_id INT NOT NULL,
reports_to_id INT,
picture BLOB DEFAULT NULL,
email VARCHAR(50) DEFAULT NULL,
store_id INT NOT NULL,
Expand All @@ -326,6 +327,7 @@ CREATE TABLE staff (
KEY idx_fk_store_id (store_id),
KEY idx_fk_address_id (address_id),
CONSTRAINT fk_staff_store FOREIGN KEY (store_id) REFERENCES store (store_id) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT fk_staff_staff FOREIGN KEY (reports_to_id) REFERENCES staff (staff_id) ON DELETE RESTRICT ON UPDATE CASCADE,
CONSTRAINT fk_staff_address FOREIGN KEY (address_id) REFERENCES address (address_id) ON DELETE RESTRICT ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Expand Down
2 changes: 1 addition & 1 deletion examples/mysql/src/entities/customer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pub struct Model {
pub address_id: i32,
pub active: i8,
pub create_date: DateTime,
pub last_update: Option<DateTimeUtc>,
pub last_update: DateTimeUtc,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation, seaography::macros::RelationsCompact)]
Expand Down
8 changes: 1 addition & 7 deletions examples/mysql/src/entities/film_text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,7 @@ pub struct Model {
pub description: Option<String>,
}

#[derive(Copy, Clone, Debug, EnumIter, seaography::macros::RelationsCompact)]
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation, seaography::macros::RelationsCompact)]
pub enum Relation {}

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}

impl ActiveModelBehavior for ActiveModel {}
8 changes: 1 addition & 7 deletions examples/mysql/src/entities/language.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,7 @@ pub struct Model {
pub last_update: DateTimeUtc,
}

#[derive(Copy, Clone, Debug, EnumIter, seaography::macros::RelationsCompact)]
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation, seaography::macros::RelationsCompact)]
pub enum Relation {}

impl RelationTrait for Relation {
fn def(&self) -> RelationDef {
panic!("No RelationDef")
}
}

impl ActiveModelBehavior for ActiveModel {}
2 changes: 1 addition & 1 deletion examples/mysql/src/entities/payment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ pub struct Model {
#[sea_orm(column_type = "Decimal(Some((5, 2)))")]
pub amount: Decimal,
pub payment_date: DateTime,
pub last_update: Option<DateTimeUtc>,
pub last_update: DateTimeUtc,
}

#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation, seaography::macros::RelationsCompact)]
Expand Down
9 changes: 9 additions & 0 deletions examples/mysql/src/entities/staff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ pub struct Model {
pub first_name: String,
pub last_name: String,
pub address_id: i32,
pub reports_to_id: Option<i32>,
pub picture: Option<Vec<u8>>,
pub email: Option<String>,
pub store_id: i32,
Expand Down Expand Up @@ -44,6 +45,14 @@ pub enum Relation {
on_delete = "Restrict"
)]
Store,
#[sea_orm(
belongs_to = "Entity",
from = "Column::ReportsToId",
to = "Column::StaffId",
on_update = "Restrict",
on_delete = "Restrict"
)]
SelfRef,
#[sea_orm(has_many = "super::payment::Entity")]
Payment,
#[sea_orm(has_many = "super::rental::Entity")]
Expand Down
2 changes: 0 additions & 2 deletions examples/mysql/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ async fn main() {
.with_max_level(tracing::Level::INFO)
.with_test_writer()
.init();

let database = Database::connect(&*DATABASE_URL)
.await
.expect("Fail to initialize database connection");
Expand All @@ -61,7 +60,6 @@ async fn main() {
&*ENDPOINT,
get(graphql_playground).post(GraphQL::new(schema)),
);

println!("Visit GraphQL Playground at http://{}", *URL);
Server::new(TcpListener::bind(&*URL))
.run(app)
Expand Down
60 changes: 59 additions & 1 deletion examples/mysql/tests/query_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,62 @@ async fn test_cursor_pagination_no_next() {
}
"#,
)
}
}

#[tokio::test]
async fn test_self_ref() {
let schema = get_schema().await;

assert_eq(
schema
.execute(
r#"
{
staff {
nodes {
firstName
reportsToId
selfRefReverse {
staffId
firstName
}
selfRef {
staffId
firstName
}
}
}
}
"#,
)
.await,
r#"
{
"staff": {
"nodes": [
{
"firstName": "Mike",
"reportsToId": null,
"selfRefReverse": [
{
"staffId": 2,
"firstName": "Jon"
}
],
"selfRef": null
},
{
"firstName": "Jon",
"reportsToId": 1,
"selfRefReverse": null,
"selfRef": {
"staffId": 1,
"firstName": "Mike"
}
}
]
}
}
"#,
)
}
Loading

0 comments on commit d7f1626

Please sign in to comment.