From 641fcddc406e2073999a7cf55fe0c0d184412f65 Mon Sep 17 00:00:00 2001 From: Antonio Yang Date: Tue, 27 Jul 2021 14:48:08 +0800 Subject: [PATCH] Implement #[belongs_to] for one to many relationship (#135) - Implement #[belongs_to] and add table relation example - Hot fix (#136) --- examples/rdb-contract/src/lib.rs | 40 +++++++++++++++- examples/rdb-contract/src/modules.rs | 11 ++++- sewup-derive/src/lib.rs | 70 +++++++++++++++++++++------- 3 files changed, 102 insertions(+), 19 deletions(-) diff --git a/examples/rdb-contract/src/lib.rs b/examples/rdb-contract/src/lib.rs index 003299ab2..1e9a75d57 100644 --- a/examples/rdb-contract/src/lib.rs +++ b/examples/rdb-contract/src/lib.rs @@ -1,15 +1,23 @@ +use serde_derive::{Deserialize, Serialize}; + use sewup::rdb::errors::Error as LibError; use sewup_derive::{ewasm_fn, ewasm_fn_sig, ewasm_main, ewasm_test}; mod errors; mod modules; -use modules::{person, Person, PERSON}; +use modules::{person, post, Person, Post, PERSON, POST}; + +#[derive(Serialize, Deserialize)] +pub struct Input { + id: usize, +} #[ewasm_fn] fn init_db_with_tables() -> anyhow::Result { let mut db = sewup::rdb::Db::new()?; db.create_table::(); + db.create_table::(); db.commit()?; Ok(().into()) } @@ -77,6 +85,20 @@ fn get_childern() -> anyhow::Result { Ok(sewup::primitives::EwasmAny::from(protocol)) } +#[ewasm_fn] +fn get_post_author(input: Input) -> anyhow::Result { + let table = sewup::rdb::Db::load(None)?.table::()?; + let post = table.get_record(input.id)?; + + // ( Person <- 1 --- many -> Post ) + // use relationship to get the post owner + let owner = post.person()?; + + // This is an example show output not wrappered into protocol, + // just return instance itself + Ok(sewup::primitives::EwasmAny::from(owner)) +} + #[ewasm_main(auto)] fn main() -> anyhow::Result { use sewup_derive::ewasm_input_from; @@ -87,9 +109,14 @@ fn main() -> anyhow::Result { ewasm_fn_sig!(person::create) => ewasm_input_from!(contract move person::create), ewasm_fn_sig!(person::update) => ewasm_input_from!(contract move person::update), ewasm_fn_sig!(person::delete) => ewasm_input_from!(contract move person::delete), + ewasm_fn_sig!(post::get) => ewasm_input_from!(contract move post::get), + ewasm_fn_sig!(post::create) => ewasm_input_from!(contract move post::create), + ewasm_fn_sig!(post::update) => ewasm_input_from!(contract move post::update), + ewasm_fn_sig!(post::delete) => ewasm_input_from!(contract move post::delete), ewasm_fn_sig!(check_version_and_features) => { check_version_and_features(0, vec![sewup::rdb::Feature::Default]) } + ewasm_fn_sig!(get_post_author) => ewasm_input_from!(contract move get_post_author), ewasm_fn_sig!(get_childern) => get_childern(), ewasm_fn_sig!(init_db_with_tables) => init_db_with_tables(), ewasm_fn_sig!(check_tables) => check_tables(), @@ -118,10 +145,21 @@ mod tests { expect_output.set_id(1); ewasm_auto_assert_eq!(person::create(create_input), expect_output); + let post = Post { + words: 100, + person_id: 1, + }; + let mut create_post_input = post::protocol(post); + let mut expect_post_output = create_post_input.clone(); + expect_post_output.set_id(1); + ewasm_auto_assert_eq!(post::create(create_post_input), expect_post_output); + let mut get_input: person::Protocol = Person::default().into(); get_input.set_id(1); ewasm_auto_assert_eq!(person::get(get_input), expect_output); + ewasm_auto_assert_eq!(get_post_author(Input { id: 1 }), person); + let child = Person { trusted: false, age: 9, diff --git a/examples/rdb-contract/src/modules.rs b/examples/rdb-contract/src/modules.rs index a7783d7ad..4e7829dae 100644 --- a/examples/rdb-contract/src/modules.rs +++ b/examples/rdb-contract/src/modules.rs @@ -5,8 +5,17 @@ use sewup_derive::Table; // to communicate with these handler, you will need protocol. // The protocol is easy to build by the `{struct_name}::protocol`, `{struct_name}::Protocol`, // please check out the test case in the end of this document -#[derive(Table, Default, Clone, Serialize, Deserialize)] +#[derive(Table, Default, Copy, Clone, PartialEq, Serialize, Deserialize)] pub struct Person { pub(crate) trusted: bool, pub(crate) age: u8, } + +#[derive(Table, Default, Copy, Clone, PartialEq, Serialize, Deserialize)] +#[belongs_to(Person)] +pub struct Post { + pub(crate) words: u8, + + // Curretly, this field need to set up manually, this will be enhance later + pub(crate) person_id: usize, +} diff --git a/sewup-derive/src/lib.rs b/sewup-derive/src/lib.rs index 39a940695..bbfb99f01 100644 --- a/sewup-derive/src/lib.rs +++ b/sewup-derive/src/lib.rs @@ -613,9 +613,26 @@ pub fn derive_value(item: TokenStream) -> TokenStream { /// assert!(default_input != default_person_input) /// ``` #[cfg(feature = "rdb")] -#[proc_macro_derive(Table)] +#[proc_macro_derive(Table, attributes(belongs_to, belongs_none_or))] pub fn derive_table(item: TokenStream) -> TokenStream { let input = syn::parse_macro_input!(item as syn::DeriveInput); + let attrs = &input.attrs; + let mut belongs_to: Option = None; + for a in attrs.iter() { + let syn::Attribute { path, tokens, .. } = a; + let attr_name = path.segments.first().map(|s| s.ident.to_string()); + if Some("belongs_to".to_string()) == attr_name { + belongs_to = Some( + tokens + .to_string() + .strip_prefix('(') + .expect("#[belongs_to(table_name)] is not correct") + .strip_suffix(')') + .expect("#[belongs_to(table_name)] is not correct") + .to_string(), + ); + } + } let struct_name = &input.ident; let fields_with_type = match &input.data { syn::Data::Struct(syn::DataStruct { @@ -660,16 +677,15 @@ pub fn derive_table(item: TokenStream) -> TokenStream { let protocol_name = Ident::new(&format!("{}Protocol", struct_name), Span::call_site()); let wrapper_name = Ident::new(&format!("{}Wrapper", struct_name), Span::call_site()); - let captal_mod_name = Ident::new( + let captal_name = Ident::new( &format!("{}", struct_name).to_ascii_uppercase(), Span::call_site(), ); - let lower_mod_name = Ident::new( + let lower_name = Ident::new( &format!("{}", struct_name).to_ascii_lowercase(), Span::call_site(), ); - - quote!( + let mut output = quote!( impl sewup::rdb::traits::Record for #struct_name {} #[derive(Clone, sewup::Serialize, sewup::Deserialize)] @@ -738,7 +754,7 @@ pub fn derive_table(item: TokenStream) -> TokenStream { } } } - pub mod #captal_mod_name { + pub mod #captal_name { use sewup_derive::ewasm_fn_sig; pub(crate) const GET_SIG: [u8; 4] = ewasm_fn_sig!(#struct_name::get()); pub(crate) const CREATE_SIG: [u8; 4] = ewasm_fn_sig!(#struct_name::create()); @@ -766,14 +782,14 @@ pub fn derive_table(item: TokenStream) -> TokenStream { } } #[cfg(target_arch = "wasm32")] - pub mod #lower_mod_name { + pub mod #lower_name { use super::*; use sewup::primitives::IntoEwasmAny; pub type Protocol = #protocol_name; pub type Wrapper = #wrapper_name; - pub type _Instance = #struct_name; + pub type _InstanceType = #struct_name; pub fn get(proc: Protocol) -> sewup::Result { - let table = sewup::rdb::Db::load(None)?.table::<_Instance>()?; + let table = sewup::rdb::Db::load(None)?.table::<_InstanceType>()?; if proc.filter { let mut raw_output: Vec = Vec::new(); for r in table.all_records()?.drain(..){ @@ -818,21 +834,21 @@ pub fn derive_table(item: TokenStream) -> TokenStream { } } pub fn create(proc: Protocol) -> sewup::Result { - let mut table = sewup::rdb::Db::load(None)?.table::<_Instance>()?; + let mut table = sewup::rdb::Db::load(None)?.table::<_InstanceType>()?; let mut output_proc = proc.clone(); output_proc.records[0].id = Some(table.add_record(proc.records[0].into())?); table.commit()?; Ok(output_proc.into_ewasm_any()) } pub fn update(proc: Protocol) -> sewup::Result { - let mut table = sewup::rdb::Db::load(None)?.table::<_Instance>()?; + let mut table = sewup::rdb::Db::load(None)?.table::<_InstanceType>()?; let id = proc.records[0].id.unwrap_or_default(); table.update_record(id, Some(proc.records[0].clone().into()))?; table.commit()?; Ok(proc.into_ewasm_any()) } pub fn delete(proc: Protocol) -> sewup::Result { - let mut table = sewup::rdb::Db::load(None)?.table::<_Instance>()?; + let mut table = sewup::rdb::Db::load(None)?.table::<_InstanceType>()?; let id = proc.records[0].id.unwrap_or_default(); table.update_record(id, None)?; table.commit()?; @@ -840,15 +856,15 @@ pub fn derive_table(item: TokenStream) -> TokenStream { } } #[cfg(not(target_arch = "wasm32"))] - pub mod #lower_mod_name { + pub mod #lower_name { use super::*; pub type Protocol = #protocol_name; pub type Wrapper = #wrapper_name; - pub type _Instance = #struct_name; + pub type _InstanceType = #struct_name; pub type Query = Wrapper; #[inline] - pub fn protocol(instance: _Instance) -> Protocol { + pub fn protocol(instance: _InstanceType) -> Protocol { instance.into() } impl Protocol { @@ -860,7 +876,7 @@ pub fn derive_table(item: TokenStream) -> TokenStream { true } } - pub fn query(instance: _Instance) -> Wrapper { + pub fn query(instance: _InstanceType) -> Wrapper { instance.into() } @@ -874,7 +890,27 @@ pub fn derive_table(item: TokenStream) -> TokenStream { } } } - ).into() + ).to_string(); + + if let Some(parent_table) = belongs_to { + let lower_parent_table = &format!("{}", &parent_table).to_ascii_lowercase(); + let parent_table = Ident::new(&parent_table, Span::call_site()); + let lower_parent_table_ident = Ident::new(&lower_parent_table, Span::call_site()); + let field_name = &format!("{}_id", lower_parent_table); + + output += "e! { + impl #struct_name { + pub fn #lower_parent_table_ident (&self) -> sewup::Result<#parent_table> { + let id: usize = sewup::utils::get_field_by_name(self, #field_name); + let parent_table = sewup::rdb::Db::load(None)?.table::<#parent_table>()?; + parent_table.get_record(id) + } + } + } + .to_string(); + } + + output.parse().unwrap() } /// helps you setup the test mododule, and test cases in contract.