Skip to content

Commit

Permalink
Add utilities to seaography crate for Relations derive
Browse files Browse the repository at this point in the history
  • Loading branch information
karatakis authored Oct 21, 2022
1 parent d50cea4 commit 39ced31
Show file tree
Hide file tree
Showing 5 changed files with 135 additions and 93 deletions.
2 changes: 1 addition & 1 deletion derive/src/enumeration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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>,
Expand Down
4 changes: 2 additions & 2 deletions derive/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Vec<Box<Filter>>>,
Expand Down Expand Up @@ -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),*
Expand Down
109 changes: 21 additions & 88 deletions derive/src/relation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand All @@ -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<H: std::hash::Hasher>(&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::Filter>, Option<#path::OrderBy>>);

#[async_trait::async_trait]
impl async_graphql::dataloader::Loader<#foreign_key_name> for crate::OrmDataloader {
Expand All @@ -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)
}
}
Expand All @@ -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();

Expand Down
2 changes: 1 addition & 1 deletion discoverer/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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,
Expand Down
111 changes: 110 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -141,7 +143,7 @@ pub enum OrderByEnum {

pub type BinaryVector = Vec<u8>;

#[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)))]
Expand Down Expand Up @@ -507,3 +509,110 @@ impl async_graphql::types::connection::CursorType for CursorValues {
.join(",")
}
}

#[derive(Debug, Clone)]
pub struct RelationKeyStruct<Filter, Order>(pub sea_orm::Value, pub Filter, pub Order);

impl<Filter, Order> PartialEq for RelationKeyStruct<Filter, Order> {
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<Filter, Order> Eq for RelationKeyStruct<Filter, Order> {}

impl<Filter, Order> std::hash::Hash for RelationKeyStruct<Filter, Order> {
fn hash<H: std::hash::Hasher>(&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<Entity, Filter, Order>(
keys: Vec<RelationKeyStruct<Option<Filter>, Option<Order>>>,
relation: sea_orm::RelationDef,
db: &sea_orm::DatabaseConnection,
) -> std::result::Result<
Vec<(
RelationKeyStruct<Option<Filter>, Option<Order>>,
<Entity as sea_orm::EntityTrait>::Model,
)>,
sea_orm::error::DbErr,
>
where
Entity: sea_orm::EntityTrait,
<Entity::Column as FromStr>::Err: Debug,
{
use heck::ToSnakeCase;
use sea_orm::prelude::*;

let keys: Vec<sea_orm::Value> = keys.into_iter().map(|key| key.0).collect();

// TODO support multiple columns
let to_column =
<Entity::Column as FromStr>::from_str(relation.to_col.to_string().to_snake_case().as_str())
.unwrap();

let stmt = <Entity as sea_orm::EntityTrait>::find();

let stmt =
<sea_orm::Select<Entity> as sea_orm::QueryFilter>::filter(stmt, to_column.is_in(keys));

let data = stmt.all(db).await?.into_iter().map(
|model: <Entity as EntityTrait>::Model| -> (
RelationKeyStruct<Option<Filter>, Option<Order>>,
<Entity as EntityTrait>::Model,
) {
let key = RelationKeyStruct::<Option<Filter>, Option<Order>>(
model.get(to_column),
None,
None,
);

(key, model)
},
);

Ok(data.collect())
}

0 comments on commit 39ced31

Please sign in to comment.