diff --git a/derive/src/enumeration.rs b/derive/src/enumeration.rs index 610a97e6..9212a72e 100644 --- a/derive/src/enumeration.rs +++ b/derive/src/enumeration.rs @@ -5,7 +5,7 @@ pub fn enum_filter_fn(ident: syn::Ident) -> TokenStream { let name = format_ident!("{}EnumFilter", ident); quote! { - #[derive(Debug, async_graphql::InputObject)] + #[derive(Debug, Clone, async_graphql::InputObject)] pub struct #name { pub eq: Option<#ident>, pub ne: Option<#ident>, diff --git a/derive/src/filter.rs b/derive/src/filter.rs index 01ef74b5..02f6b544 100644 --- a/derive/src/filter.rs +++ b/derive/src/filter.rs @@ -120,7 +120,7 @@ pub fn filter_struct( // } Ok(quote! { - #[derive(Debug, async_graphql::InputObject)] + #[derive(Debug, Clone, async_graphql::InputObject)] #[graphql(name = #filter_name)] pub struct Filter { pub or: Option>>, @@ -151,7 +151,7 @@ pub fn order_by_struct( let filter_name = format!("{}OrderBy", entity_name.value().to_upper_camel_case()); Ok(quote! { - #[derive(Debug, async_graphql::InputObject)] + #[derive(Debug, Clone, async_graphql::InputObject)] #[graphql(name = #filter_name)] pub struct OrderBy { #(#fields),* diff --git a/derive/src/relation.rs b/derive/src/relation.rs index 89360c42..40c9cf68 100644 --- a/derive/src/relation.rs +++ b/derive/src/relation.rs @@ -175,24 +175,24 @@ pub fn relation_fn( )); }; - let target_path = if target_path.ne("Entity") { - &target_path.as_str()[..target_path.len() - 6] + let path: TokenStream = if target_path.ne("Entity") { + target_path.as_str()[..target_path.len() - 8] + .parse() + .unwrap() } else { - "" + return Err(crate::error::Error::Internal( + "Cannot parse entity path".into(), + )); }; - let target_entity: TokenStream = format!("{}Entity", target_path).parse()?; - let target_column: TokenStream = format!("{}Column", target_path).parse()?; - let target_model: TokenStream = format!("{}Model", target_path).parse()?; - let (return_type, extra_imports, map_method) = if has_many.is_some() { ( - quote! { Vec<#target_model> }, + quote! { Vec<#path::Model> }, quote! { use seaography::itertools::Itertools; }, quote! { .into_group_map() }, ) } else if belongs_to.is_some() { - (quote! { #target_model }, quote! {}, quote! { .collect() }) + (quote! { #path::Model }, quote! {}, quote! { .collect() }) } else { return Err(crate::error::Error::Internal( "Cannot map relation: neither one-many or many-one".into(), @@ -204,62 +204,8 @@ pub fn relation_fn( Ok(( quote! { - #[derive(Clone, Debug)] - pub struct #foreign_key_name(pub sea_orm::Value); - - impl PartialEq for #foreign_key_name { - fn eq(&self, other: &Self) -> bool { - // TODO temporary hack to solve the following problem - // let v1 = TestFK(sea_orm::Value::TinyInt(Some(1))); - // let v2 = TestFK(sea_orm::Value::Int(Some(1))); - // println!("Result: {}", v1.eq(&v2)); - - fn split_at_nth_char(s: &str, p: char, n: usize) -> Option<(&str, &str)> { - s.match_indices(p).nth(n).map(|(index, _)| s.split_at(index)) - } - - - let a = format!("{:?}", self.0); - let b = format!("{:?}", other.0); - - let a = split_at_nth_char(a.as_str(), '(', 1).map(|v| v.1); - let b = split_at_nth_char(b.as_str(), '(', 1).map(|v| v.1); - - a.eq(&b) - } - } - - impl Eq for #foreign_key_name { - } - - impl std::hash::Hash for #foreign_key_name { - fn hash(&self, state: &mut H) { - // TODO this is a hack - - fn split_at_nth_char(s: &str, p: char, n: usize) -> Option<(&str, &str)> { - s.match_indices(p).nth(n).map(|(index, _)| s.split_at(index)) - } - - let a = format!("{:?}", self.0); - let a = split_at_nth_char(a.as_str(), '(', 1).map(|v| v.1); - - a.hash(state) - // TODO else do the following - // match self.0 { - // sea_orm::Value::TinyInt(int) => int.unwrap().hash(state), - // sea_orm::Value::SmallInt(int) => int.unwrap().hash(state), - // sea_orm::Value::Int(int) => int.unwrap().hash(state), - // sea_orm::Value::BigInt(int) => int.unwrap().hash(state), - // sea_orm::Value::TinyUnsigned(int) => int.unwrap().hash(state), - // sea_orm::Value::SmallUnsigned(int) => int.unwrap().hash(state), - // sea_orm::Value::Unsigned(int) => int.unwrap().hash(state), - // sea_orm::Value::BigUnsigned(int) => int.unwrap().hash(state), - // sea_orm::Value::String(str) => str.unwrap().hash(state), - // sea_orm::Value::Uuid(uuid) => uuid.unwrap().hash(state), - // _ => format!("{:?}", self.0).hash(state) - // } - } - } + #[derive(Debug, Clone, PartialEq, Eq, Hash)] + pub struct #foreign_key_name(pub seaography::RelationKeyStruct, Option<#path::OrderBy>>); #[async_trait::async_trait] impl async_graphql::dataloader::Loader<#foreign_key_name> for crate::OrmDataloader { @@ -273,37 +219,24 @@ pub fn relation_fn( use seaography::heck::ToSnakeCase; use ::std::str::FromStr; - let key_values: Vec<_> = keys + let keys: Vec<_> = keys .into_iter() .map(|key| key.0.to_owned()) .collect(); - // TODO support multiple columns - let to_column: #target_column = #target_column::from_str( - #relation_enum - .def() - .to_col - .to_string() - .to_snake_case() - .as_str() - ).unwrap(); - #extra_imports - let data: std::collections::HashMap<#foreign_key_name, Self::Value> = #target_entity::find() - .filter( - to_column.is_in(key_values) - ) - .all(&self.db) - .await? + let data: std::collections::HashMap<#foreign_key_name, Self::Value> = seaography + ::fetch_relation_data::<#path::Entity, #path::Filter, #path::OrderBy>( + keys, + #relation_enum.def(), + &self.db, + ).await? .into_iter() - .map(|model| { - let key = #foreign_key_name(model.get(to_column)); - - (key, model) - }) + .map(|(key, model)| (#foreign_key_name(key), model)) #map_method; + Ok(data) } } @@ -329,7 +262,7 @@ pub fn relation_fn( .as_str() ).unwrap(); - let key = #foreign_key_name(self.get(from_column)); + let key = #foreign_key_name(seaography::RelationKeyStruct(self.get(from_column), None, None)); let data: Option<_> = data_loader.load_one(key).await.unwrap(); diff --git a/discoverer/src/lib.rs b/discoverer/src/lib.rs index a322050e..ee5fcf29 100644 --- a/discoverer/src/lib.rs +++ b/discoverer/src/lib.rs @@ -1,7 +1,7 @@ use sea_schema::sea_query::TableCreateStatement; use std::collections::BTreeMap; -#[derive(Clone, Debug, Eq, PartialEq, Hash)] +#[derive(Debug, Clone, Eq, PartialEq, Hash)] pub enum SqlVersion { Sqlite, Mysql, diff --git a/src/lib.rs b/src/lib.rs index bba949d2..c016848e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,8 @@ //! //! Seaography is a community driven project. We welcome you to participate, contribute and together build for Rust's future. +use std::{fmt::Debug, str::FromStr}; + pub use heck; pub use itertools; use itertools::Itertools; @@ -141,7 +143,7 @@ pub enum OrderByEnum { pub type BinaryVector = Vec; -#[derive(Debug, async_graphql::InputObject)] +#[derive(Debug, Clone, async_graphql::InputObject)] #[graphql(concrete(name = "StringFilter", params(String)))] #[graphql(concrete(name = "TinyIntegerFilter", params(i8)))] #[graphql(concrete(name = "SmallIntegerFilter", params(i16)))] @@ -507,3 +509,110 @@ impl async_graphql::types::connection::CursorType for CursorValues { .join(",") } } + +#[derive(Debug, Clone)] +pub struct RelationKeyStruct(pub sea_orm::Value, pub Filter, pub Order); + +impl PartialEq for RelationKeyStruct { + fn eq(&self, other: &Self) -> bool { + // TODO temporary hack to solve the following problem + // let v1 = TestFK(sea_orm::Value::TinyInt(Some(1))); + // let v2 = TestFK(sea_orm::Value::Int(Some(1))); + // println!("Result: {}", v1.eq(&v2)); + + fn split_at_nth_char(s: &str, p: char, n: usize) -> Option<(&str, &str)> { + s.match_indices(p) + .nth(n) + .map(|(index, _)| s.split_at(index)) + } + + let a = format!("{:?}", self.0); + let b = format!("{:?}", other.0); + + let a = split_at_nth_char(a.as_str(), '(', 1).map(|v| v.1); + let b = split_at_nth_char(b.as_str(), '(', 1).map(|v| v.1); + + a.eq(&b) + } +} + +impl Eq for RelationKeyStruct {} + +impl std::hash::Hash for RelationKeyStruct { + fn hash(&self, state: &mut H) { + // TODO this is a hack + + fn split_at_nth_char(s: &str, p: char, n: usize) -> Option<(&str, &str)> { + s.match_indices(p) + .nth(n) + .map(|(index, _)| s.split_at(index)) + } + + let a = format!("{:?}", self.0); + let a = split_at_nth_char(a.as_str(), '(', 1).map(|v| v.1); + + a.hash(state) + // TODO else do the following + // match self.0 { + // sea_orm::Value::TinyInt(int) => int.unwrap().hash(state), + // sea_orm::Value::SmallInt(int) => int.unwrap().hash(state), + // sea_orm::Value::Int(int) => int.unwrap().hash(state), + // sea_orm::Value::BigInt(int) => int.unwrap().hash(state), + // sea_orm::Value::TinyUnsigned(int) => int.unwrap().hash(state), + // sea_orm::Value::SmallUnsigned(int) => int.unwrap().hash(state), + // sea_orm::Value::Unsigned(int) => int.unwrap().hash(state), + // sea_orm::Value::BigUnsigned(int) => int.unwrap().hash(state), + // sea_orm::Value::String(str) => str.unwrap().hash(state), + // sea_orm::Value::Uuid(uuid) => uuid.unwrap().hash(state), + // _ => format!("{:?}", self.0).hash(state) + // } + } +} + +pub async fn fetch_relation_data( + keys: Vec, Option>>, + relation: sea_orm::RelationDef, + db: &sea_orm::DatabaseConnection, +) -> std::result::Result< + Vec<( + RelationKeyStruct, Option>, + ::Model, + )>, + sea_orm::error::DbErr, +> +where + Entity: sea_orm::EntityTrait, + ::Err: Debug, +{ + use heck::ToSnakeCase; + use sea_orm::prelude::*; + + let keys: Vec = keys.into_iter().map(|key| key.0).collect(); + + // TODO support multiple columns + let to_column = + ::from_str(relation.to_col.to_string().to_snake_case().as_str()) + .unwrap(); + + let stmt = ::find(); + + let stmt = + as sea_orm::QueryFilter>::filter(stmt, to_column.is_in(keys)); + + let data = stmt.all(db).await?.into_iter().map( + |model: ::Model| -> ( + RelationKeyStruct, Option>, + ::Model, + ) { + let key = RelationKeyStruct::, Option>( + model.get(to_column), + None, + None, + ); + + (key, model) + }, + ); + + Ok(data.collect()) +}