diff --git a/edgedb-query-derive/Cargo.toml b/edgedb-query-derive/Cargo.toml index 4d36334..5cbb7ae 100644 --- a/edgedb-query-derive/Cargo.toml +++ b/edgedb-query-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "edgedb-query-derive" -version = "0.2.2" +version = "0.2.3" description = "Crate that provide a bunch of attribute macros that help to build EdgeDB query using edgedb-tokio crate" authors = ["hsedjame "] repository = "https://github.com/imagineDevit/edgedb/tree/main/edgedb-query-derive" diff --git a/edgedb-query-derive/src/builders/impl_builder.rs b/edgedb-query-derive/src/builders/impl_builder.rs index 8489b61..2d0e7e8 100644 --- a/edgedb-query-derive/src/builders/impl_builder.rs +++ b/edgedb-query-derive/src/builders/impl_builder.rs @@ -149,6 +149,7 @@ impl QueryImplBuilder { let const_check_quote = self.static_const_check_statements.clone(); + let table_name = self.table_name.clone().unwrap_or(String::new()); let struct_quote = self.build_struct(); let edge_ql_impl_quote = self.build_to_edgeql_impl(); @@ -167,7 +168,7 @@ impl QueryImplBuilder { impl edgedb_query::ToEdgeScalar for #struct_name { fn scalar() -> String { - String::default() + format!("<{}>", #table_name) } } diff --git a/edgedb-query-derive/src/constants.rs b/edgedb-query-derive/src/constants.rs index 0cf404a..114f644 100644 --- a/edgedb-query-derive/src/constants.rs +++ b/edgedb-query-derive/src/constants.rs @@ -1,11 +1,13 @@ // region other +pub const EMPTY : &str = ""; pub const SPACE : &str = " "; pub const SCALAR_TYPE: &str = "$scalar_type$"; pub const EDGEQL: &str = "$edgeql$"; pub const INF_SIGN: &str = "<"; pub const SUP_SIGN: &str = ">"; pub const AT: &str = "@"; +pub const DOLLAR: &str = "$"; pub const ID: &str= "id"; // endregion other @@ -111,6 +113,7 @@ pub const EXPECT_NON_EMPTY_LIT: &str= "Expected a non-empty string literal"; pub const EXPECT_LIT: &str = "Expected a string literal"; pub const EXPECT_TABLE: &str = "Expected a table name"; pub const EXPECT_SRC: &str = "Expected a src value"; +pub const EXPECT_VALUE: &str = "Expected a query value"; pub const EXPECT_OPERATOR: &str = "Expected filter operator attribute `#[filter(operator = \"...\")]`"; pub const UNSUPPORTED_ATTRIBUTE: &str = "Unsupported attribute"; pub const FIRST_FILTER_EXPECTED: &str = "Expected first filter attribute `#[filter(...)]`"; @@ -214,6 +217,7 @@ pub const EITHER_ONE_FILTERS_OR_FILTER_TAG_EXPECTED: &str = "SelectQuery can onl pub const EITHER_ONE_SETS_OR_SET_TAG_EXPECTED: &str = "UpdateQuery can only have either one `sets` or one or more `set` or `nested_query` fields"; pub const EXPECTED_AT_LEAST_ONE_SET_FIELD: &str = "UpdateQuery must have at least one field with #[set] attribute or with no attribute"; pub const EXPECTED_ID_FIELD: &str = "Query result struct must have an id field of type uuid::Uuid"; +pub const AT_LEAST_ONE_FIELD_ATTRIBUTE_EXPECTED: &str = "At least one field attribute is expected: 'column_name', 'param' or 'scalar'"; // endregion messages // region types diff --git a/edgedb-query-derive/src/delete_query.rs b/edgedb-query-derive/src/delete_query.rs index 9b29862..be51691 100644 --- a/edgedb-query-derive/src/delete_query.rs +++ b/edgedb-query-derive/src/delete_query.rs @@ -1,11 +1,11 @@ +use edgedb_query::QueryType; use syn::{Ident, ItemStruct}; use syn::parse::{Parse, ParseStream}; -use edgedb_query::QueryType; -use crate::constants::*; -use crate::{queries::Query, meta_data::{TableInfo, try_get_meta}}; +use crate::{meta_data::{TableInfo, try_get_meta}, queries::Query}; use crate::builders::impl_builder::QueryImplBuilder; -use crate::statements::filters::{FilterRequiredQuery, filters_from_fields, FilterStatement}; +use crate::constants::*; +use crate::statements::filters::{FilterRequiredQuery, filters_from_fields, FilterStatement, set_table_name}; #[derive(Debug, Clone)] pub struct DeleteQuery { @@ -24,7 +24,8 @@ impl DeleteQuery { } pub fn with_meta(&mut self, meta: TableInfo) -> &mut Self { - self.meta = Some(meta); + self.meta = Some(meta.clone()); + set_table_name(&mut self.filter_statement, meta.table_name()); self } } diff --git a/edgedb-query-derive/src/edgedb_filters.rs b/edgedb-query-derive/src/edgedb_filters.rs index ad67489..6782a64 100644 --- a/edgedb-query-derive/src/edgedb_filters.rs +++ b/edgedb-query-derive/src/edgedb_filters.rs @@ -40,6 +40,7 @@ impl EdgedbFilters { impl edgedb_query::queries::filter::Filter for #struct_name { fn to_edgeql(&self, table_name: &str) -> String { + use edgedb_query::{ToEdgeQl, EdgeQl}; let mut query = String::new(); #(#filters)* query diff --git a/edgedb-query-derive/src/file_query.rs b/edgedb-query-derive/src/file_query.rs index 7f47ee0..f5872d5 100644 --- a/edgedb-query-derive/src/file_query.rs +++ b/edgedb-query-derive/src/file_query.rs @@ -4,22 +4,22 @@ use regex::Regex; use syn::{Field, Ident, ItemStruct}; use syn::parse::{Parse, ParseStream}; use edgedb_query::QueryType; -use crate::constants::{PARAM, PARAM_PATTERN}; +use crate::constants::{DOLLAR, EMPTY, PARAM, PARAM_PATTERN}; use crate::builders::impl_builder::{FieldCat, QueryImplBuilder, ImplBuilderField}; -use crate::meta_data::{SrcFile, try_get_meta}; +use crate::meta_data::{SrcQuery, try_get_meta}; use crate::queries::{Query, QueryField}; use crate::tags::{build_tags_from_field, Tagged}; use crate::tags::param_tag::{ParamTag, ParamTagBuilder}; use crate::tags::TagBuilders::ParamBuilder; #[derive(Debug, Clone)] -pub struct FileQuery { +pub struct FileQuery { pub ident: Ident, - pub meta: Option, + pub meta: Option, pub params: Vec } -impl FileQuery { +impl FileQuery { pub fn new(ident: Ident) -> Self { Self { ident, @@ -28,7 +28,7 @@ impl FileQuery { } } - pub fn with_meta(&mut self, meta: SrcFile) -> &mut Self { + pub fn with_meta(&mut self, meta: T) -> &mut Self { self.meta = Some(meta); self } @@ -48,9 +48,8 @@ impl FileQuery { .map(|f| f.param()) .collect::>(); - let param_matches = param_matches.iter() - .map(|s| s.replace("$", "")) + .map(|s| s.replace(DOLLAR, EMPTY)) .collect::>(); let struct_params_not_query = params_values.clone().into_iter() @@ -58,25 +57,21 @@ impl FileQuery { .collect::>(); let query_params_not_struct = param_matches.clone().into_iter() - .filter(|s| !params_values.contains(&s.replace("$", ""))) + .filter(|s| !params_values.contains(&s.replace(DOLLAR, EMPTY))) .collect::>(); - if struct_params_not_query.len() > 0 { + if !struct_params_not_query.is_empty() { return Err( syn::Error::new_spanned( self.ident.clone(), - format!(r" - Following struct attributes do not appear as query parameters : {:#?} - ",struct_params_not_query), + format!("Following struct attributes do not appear as query parameters : {struct_params_not_query:#?}"), ) ) - } else if query_params_not_struct.len() > 0 { + } else if !query_params_not_struct.is_empty() { return Err( syn::Error::new_spanned( self.ident.clone(), - format!(r" - Following query parameters do not appear as struct attribute : {:#?} - ",query_params_not_struct), + format!("Following query parameters do not appear as struct attribute : {query_params_not_struct:#?}"), ) ) } else if param_matches != params_values { @@ -91,7 +86,7 @@ impl FileQuery { } } -impl Query for FileQuery { +impl Query for FileQuery { fn get_param_labels(&self) -> Vec<(Ident, String)> { self.params.iter() .map(|f| (f.field.ident.clone(), f.param())) @@ -126,7 +121,7 @@ impl Query for FileQuery { } } -impl Parse for FileQuery { +impl Parse for FileQuery { fn parse(input: ParseStream) -> syn::Result { let strukt = input.parse::()?; diff --git a/edgedb-query-derive/src/lib.rs b/edgedb-query-derive/src/lib.rs index 8502c8d..b25bc57 100644 --- a/edgedb-query-derive/src/lib.rs +++ b/edgedb-query-derive/src/lib.rs @@ -12,7 +12,7 @@ use crate::edgedb_enum::EdgedbEnum; use crate::edgedb_filters::EdgedbFilters; use crate::edgedb_sets::EdgedbSets; use crate::file_query::FileQuery; -use crate::meta_data::SrcFile; +use crate::meta_data::{SrcFile, SrcValue}; use crate::query_result::QueryResult; use crate::select_query::SelectQuery; use crate::update_query::UpdateQuery; @@ -321,6 +321,7 @@ pub fn delete_query(attr: TokenStream, item: TokenStream) -> TokenStream { /// /// let add_user = AddUser { /// name: "Joe".to_string(), +/// age: 18, /// friend: "Henri".to_string(), /// }; /// @@ -337,7 +338,67 @@ pub fn file_query(attr: TokenStream, item: TokenStream) -> TokenStream { let meta = parse_macro_input!(attr as SrcFile); - parse_macro_input!(item as FileQuery) + parse_macro_input!(item as FileQuery) + .with_meta(meta) + .validate() + .and_then(|q| q.to_token_stream()) + .unwrap_or_else(|e| e.to_compile_error().into()) +} + +/// Create an edgeDB query based on a source file +/// +/// ## Usage + +/// ```rust +/// use edgedb_query_derive::{query}; +/// use edgedb_query::BasicResult; +/// use edgedb_query::models::edge_query::ToEdgeQuery; +/// +/// #[query(value=r#" +/// insert users::User { +/// name := $user_name, +/// age := $age, +/// friend := ( +/// select users::User { +/// name, +/// age, +/// } +/// filter .name = $friend_name +/// ) +/// }"# +/// )] +/// pub struct AddUser { +/// #[param("user_name")] +/// pub name: String, +/// pub age: i8, +/// #[param("friend_name")] +/// pub friend: String, +/// } +/// +/// async fn main() { +/// +/// let client = edgedb_tokio::create_client().await.unwrap(); +/// +/// let add_user = AddUser { +/// name: "Joe".to_string(), +/// age: 18, +/// friend: "Henri".to_string(), +/// }; +/// +/// let query = add_user.to_edge_query(); +/// +/// let result = client +/// .query_single::(query.query.as_str(), &query.args.unwrap()) +/// .await +/// .unwrap(); +/// } +/// ``` +#[proc_macro_attribute] +pub fn query(attr: TokenStream, item: TokenStream) -> TokenStream { + + let meta = parse_macro_input!(attr as SrcValue); + + parse_macro_input!(item as FileQuery) .with_meta(meta) .validate() .and_then(|q| q.to_token_stream()) diff --git a/edgedb-query-derive/src/meta_data.rs b/edgedb-query-derive/src/meta_data.rs index 3691672..96b2917 100644 --- a/edgedb-query-derive/src/meta_data.rs +++ b/edgedb-query-derive/src/meta_data.rs @@ -5,10 +5,10 @@ use proc_macro2::Span; use quote::{quote, ToTokens}; use syn::{Ident, parse::{Parse, ParseStream}, Token}; -use crate::constants::{BASIC_RESULT, DEFAULT_MODULE, EXPECT_LIT, EXPECT_META, EXPECT_NON_EMPTY_LIT, EXPECT_SRC, EXPECT_TABLE, MODULE, RESULT, SRC, TABLE, UNSUPPORTED_ATTRIBUTE}; +use crate::constants::{BASIC_RESULT, DEFAULT_MODULE, EXPECT_LIT, EXPECT_META, EXPECT_NON_EMPTY_LIT, EXPECT_SRC, EXPECT_TABLE, MODULE, RESULT, SRC, VALUE, TABLE, UNSUPPORTED_ATTRIBUTE, EXPECT_VALUE}; macro_rules! add_meta { - ($param_name: ident, $param_value: ident, $builder: ident, $with_result: ident, $with_src: ident) => { + ($param_name: ident, $param_value: ident, $builder: ident, $with_result: ident, $with_src: ident, $with_value: ident) => { let value = $param_value.clone(); match $param_value { syn::Lit::Str(s) => { @@ -18,7 +18,7 @@ macro_rules! add_meta { EXPECT_NON_EMPTY_LIT )); } else { - $builder.arg(DataType::try_from(($param_name, $with_result, $with_src))?, s.value()); + $builder.arg(DataType::try_from(($param_name, $with_result, $with_src, $with_value))?, s.value()); } }, _ => { @@ -36,7 +36,28 @@ trait Builder { fn arg(&mut self, meta: DataType, value: String); - fn build(self) -> syn::Result; + fn build(&self) -> syn::Result; + + fn parse(&mut self, input: ParseStream, with_result: bool, with_src: bool, with_value: bool) -> syn::Result { + loop { + if !input.peek(Ident) { break; } + + let param_name = input.parse::()?; + + input.parse::()?; + + let param_value = input.parse::()?; + + add_meta!(param_name, param_value, self, with_result, with_src, with_value); + + if !input.peek(Token![,]) { + break; + } + + input.parse::()?; + } + self.build() + } } // region DataType @@ -45,13 +66,14 @@ enum DataType { Module, Table, Result, - Src + Src, + Value } -impl TryFrom<(Ident, bool, bool)> for DataType { +impl TryFrom<(Ident, bool, bool, bool)> for DataType { type Error = syn::Error; - fn try_from((value, with_result, with_src): (Ident, bool, bool)) -> Result { + fn try_from((value, with_result, with_src, with_value): (Ident, bool, bool, bool)) -> Result { match value.to_string().as_str() { MODULE => Ok(DataType::Module), @@ -70,6 +92,13 @@ impl TryFrom<(Ident, bool, bool)> for DataType { Err(syn::Error::new_spanned(value.to_token_stream(), format!("{UNSUPPORTED_ATTRIBUTE} `{value}`"))) } }, + VALUE => { + if with_value { + Ok(DataType::Value) + } else { + Err(syn::Error::new_spanned(value.to_token_stream(), format!("{UNSUPPORTED_ATTRIBUTE} `{value}`"))) + } + } _ => Err(syn::Error::new_spanned(value.to_token_stream(), format!("{UNSUPPORTED_ATTRIBUTE} `{value}`"))) } } @@ -91,27 +120,7 @@ impl TableInfo { impl Parse for TableInfo { fn parse(input: ParseStream) -> syn::Result { - let mut builder = TableInfoBuilder::default(); - - loop { - if !input.peek(Ident) { break; } - - let param_name = input.parse::()?; - - input.parse::()?; - - let param_value = input.parse::()?; - - add_meta!(param_name, param_value, builder, false, false); - - if !input.peek(Token![,]) { - break; - } - - input.parse::()?; - } - - builder.build() + TableInfoBuilder::default().parse(input, false, false, false) } } // endregion TableInfo @@ -134,10 +143,10 @@ impl Builder for TableInfoBuilder { } } - fn build(self) -> syn::Result { - if let Some(table) = self.table { + fn build(&self) -> syn::Result { + if let Some(table) = self.table.clone() { Ok(TableInfo { - module: self.module.unwrap_or(DEFAULT_MODULE.to_owned()), + module: self.module.clone().unwrap_or(DEFAULT_MODULE.to_owned()), table, }) } else { @@ -192,27 +201,7 @@ impl QueryMetaData { impl Parse for QueryMetaData { fn parse(input: ParseStream) -> syn::Result { - let mut builder = QueryMetaDataBuilder::default(); - - loop { - if !input.peek(Ident) { break; } - - let param_name = input.parse::()?; - - input.parse::()?; - - let param_value = input.parse::()?; - - add_meta!(param_name, param_value, builder, true, false); - - if !input.peek(Token![,]) { - break; - } - - input.parse::()?; - } - - builder.build() + QueryMetaDataBuilder::default().parse(input,true, false, false) } } @@ -236,22 +225,29 @@ impl Builder for QueryMetaDataBuilder { } } - fn build(self) -> syn::Result { + fn build(&self) -> syn::Result { Ok(QueryMetaData { meta: self.meta_builder.build()?, - result: self.result, + result: self.result.clone(), }) } } // endregion QueryMetaDataBuilder +// region SrcQuery + +pub trait SrcQuery { + fn get_content(&self, ident: &Ident) -> syn::Result; +} + + // region SrcFile #[derive(Debug, Clone)] pub struct SrcFile(pub(crate) String); -impl SrcFile { - pub fn get_content(&self, ident: &Ident) -> syn::Result { +impl SrcQuery for SrcFile { + fn get_content(&self, ident: &Ident) -> syn::Result { let mut s = String::default(); @@ -297,31 +293,9 @@ impl SrcFile { impl Parse for SrcFile { fn parse(input: ParseStream) -> syn::Result { - let mut builder = SrcFileBuilder::default(); - - loop { - if !input.peek(Ident) { break; } - - let param_name = input.parse::()?; - - input.parse::()?; - - let param_value = input.parse::()?; - - add_meta!(param_name, param_value, builder, false, true); - - if !input.peek(Token![,]) { - break; - } - - input.parse::()?; - } - - builder.build() + SrcFileBuilder::default().parse(input,false, true, false) } } - - // endregion SrcFile // region SrcFileBuilder @@ -339,8 +313,8 @@ impl Builder for SrcFileBuilder { } } - fn build(self) -> syn::Result { - if let Some(src) = self.src { + fn build(&self) -> syn::Result { + if let Some(src) = self.src.clone() { Ok(SrcFile(src)) } else { Err(syn::Error::new_spanned( @@ -353,6 +327,54 @@ impl Builder for SrcFileBuilder { // endregion SrcFileBuilder +#[derive(Debug, Clone)] +pub struct SrcValue(String); + +impl SrcQuery for SrcValue { + fn get_content(&self, ident: &Ident) -> syn::Result { + let value = self.0.clone(); + if value.is_empty() { + return Err(syn::Error::new_spanned(ident.to_token_stream(), "Query value cannot be empty")) + } + Ok(value) + } +} + +#[derive(Default)] +pub struct SrcValueBuilder { + pub value: Option +} + +impl Builder for SrcValueBuilder { + type T = SrcValue; + + fn arg(&mut self, meta: DataType, value: String) { + if let DataType::Value = meta { + self.value = Some(value) + } + } + + fn build(&self) -> syn::Result { + if let Some(value) = self.value.clone() { + Ok(SrcValue(value)) + } else { + Err(syn::Error::new_spanned( + VALUE.to_token_stream(), + EXPECT_VALUE, + )) + } + } +} + +impl Parse for SrcValue { + fn parse(input: ParseStream) -> syn::Result { + SrcValueBuilder::default().parse(input,false, false, true) + } +} +// endregion SrcQuery + + + // region Common functions pub fn try_get_meta(struct_name: &Ident, get_meta: F) -> syn::Result diff --git a/edgedb-query-derive/src/queries.rs b/edgedb-query-derive/src/queries.rs index 94cc7ae..c2b58f9 100644 --- a/edgedb-query-derive/src/queries.rs +++ b/edgedb-query-derive/src/queries.rs @@ -2,12 +2,12 @@ use proc_macro::TokenStream; use std::convert::TryFrom; use syn::{Field, Ident, Type}; use quote::{quote, ToTokens}; -use crate::constants::{EDGEQL, OPTION, EXPECTED_ONLY_TAGS}; +use crate::constants::{OPTION, EXPECTED_ONLY_TAGS}; use crate::utils::attributes_utils::has_only_attributes; use crate::utils::derive_utils::{element_shape, element_value}; use crate::utils::type_utils::is_type_name; use crate::builders::impl_builder::QueryImplBuilder; -use crate::tags::set_tag::{SetOption, SetTag}; + pub trait Query{ fn get_param_labels(&self) -> Vec<(Ident, String)>; @@ -53,53 +53,6 @@ impl QueryField { element_value(field_name, is_type_name(&field_type, OPTION)) } - pub fn build_nested_statement(&self, set_tag: Option) -> String { - if let Some(tag) = set_tag { - let sign = tag.option.statement(); - let column_name = self.ident.clone(); - match tag.option { - SetOption::Assign => format!("{column_name} {sign} ({EDGEQL}), "), - SetOption::Concat => format!("{column_name} := .{column_name} ++ ({EDGEQL}), "), - SetOption::Push => format!("{column_name} += ({EDGEQL}), "), - SetOption::Remove => format!("{column_name} -= ({EDGEQL}), ") - } - } else { - format!("{} := ({}), ", self.ident, EDGEQL) - } - - } - - pub fn add_stmt_quote(&self, to_query: bool, set_tag: Option, table_name: Option) -> proc_macro2::TokenStream { - let f_name = self.ident.clone(); - - let edge_ql = EDGEQL.to_string(); - - let field_statement = self.build_nested_statement(set_tag); - - let parent_table_name = table_name.unwrap_or(String::default()); - - let add_quote = if to_query { - quote!(query.push_str(p.as_str());) - } else { - quote!(set_stmt.push_str(p.as_str());) - }; - - quote! { - - let mut nested_edgeql = self.#f_name.to_edgeql(); - let nested_table_name = nested_edgeql.table_name.clone(); - - if #parent_table_name == nested_table_name.as_str() { - nested_edgeql = nested_edgeql.detached(); - } - - let p = #field_statement.to_owned() - .replace(#edge_ql, nested_edgeql.to_string().as_str()); - - #add_quote - } - } - } impl TryFrom<(&Field, Vec<&str>)> for QueryField { diff --git a/edgedb-query-derive/src/select_query.rs b/edgedb-query-derive/src/select_query.rs index 5bb89be..3ddd266 100644 --- a/edgedb-query-derive/src/select_query.rs +++ b/edgedb-query-derive/src/select_query.rs @@ -1,15 +1,15 @@ use std::convert::TryFrom; +use edgedb_query::QueryType; use quote::{quote, ToTokens}; use syn::{Field, Ident, ItemStruct}; use syn::parse::{Parse, ParseStream}; -use edgedb_query::QueryType; +use crate::builders::impl_builder::{FieldCat, ImplBuilderField, QueryImplBuilder}; use crate::constants::*; -use crate::queries::{Query, QueryField}; use crate::meta_data::{QueryMetaData, try_get_meta}; -use crate::builders::impl_builder::{FieldCat, QueryImplBuilder, ImplBuilderField}; -use crate::statements::filters::{FilterRequiredQuery, filters_from_fields, FilterStatement}; +use crate::queries::{Query, QueryField}; +use crate::statements::filters::{FilterRequiredQuery, filters_from_fields, FilterStatement, set_table_name}; use crate::utils::attributes_utils::has_attribute; use crate::utils::type_utils::is_type_name; @@ -33,7 +33,8 @@ impl SelectQuery { } pub fn with_meta(&mut self, meta: QueryMetaData) -> &mut Self { - self.meta = Some(meta); + self.meta = Some(meta.clone()); + set_table_name(&mut self.filter_statement, meta.table_name()); self } } diff --git a/edgedb-query-derive/src/statements/filters.rs b/edgedb-query-derive/src/statements/filters.rs index 5da9d35..37e000e 100644 --- a/edgedb-query-derive/src/statements/filters.rs +++ b/edgedb-query-derive/src/statements/filters.rs @@ -7,14 +7,50 @@ use syn::punctuated::Iter; use crate::constants::*; use crate::builders::impl_builder::{FieldCat, ImplBuilderField}; use crate::queries::{check_duplicate_parameter_labels, QueryField}; +use crate::statements::nested_query::{NestedQueryField, NestedQueryParentType}; use crate::tags::{build_tags_from_field, Tagged}; use crate::tags::field_tag::{FieldTag, FieldTagBuilder}; use crate::tags::filter_tag::{FilterTagBuilder, FilterTags}; use crate::tags::TagBuilders::{FieldBuilder, FilterBuilder}; use crate::utils::attributes_utils::{has_any_attribute, has_attribute, has_none_attribute}; use crate::utils::derive_utils::{nested_element_shape, nested_element_value}; -use crate::utils::type_utils::get_scalar; +use crate::utils::type_utils::{get_scalar, get_type_name}; +#[derive(Debug, Clone)] +pub enum QueryFilterStatement { + SimpleField(QueryFilter), + NestedQuery(NestedQueryField) +} + +impl QueryFilterStatement { + pub fn field(&self) -> QueryField { + match self { + QueryFilterStatement::SimpleField(qf) => qf.field.clone(), + QueryFilterStatement::NestedQuery(nq) => nq.field.clone() + } + } + + pub fn field_tag(&self) -> FieldTag { + match self { + QueryFilterStatement::SimpleField(qf) => qf.field_tag.clone(), + QueryFilterStatement::NestedQuery(nq) => nq.filter.as_ref().map(|f| f.field_tag.clone()).unwrap() + } + } + + pub fn build_statement(&self, table_name: impl Into) -> syn::Result { + match self { + QueryFilterStatement::SimpleField(qf) => qf.build_statement(table_name, false), + QueryFilterStatement::NestedQuery(nq) => nq.build_statement() + } + } + + pub fn push_to_query_quote(&self, filter_stmt: String, from_filters: bool) -> proc_macro2::TokenStream { + match self { + QueryFilterStatement::SimpleField(f) => f.push_to_query_quote(filter_stmt, from_filters), + QueryFilterStatement::NestedQuery(nq) => nq.query_statement_quote() + } + } +} // region QueryFilter #[derive(Debug, Clone)] @@ -25,10 +61,10 @@ pub struct QueryFilter { } impl QueryFilter { - pub fn build_statement(&self, table_name: impl Into) -> syn::Result { + pub fn build_statement(&self, table_name: impl Into, is_nested: bool) -> syn::Result { let filter_operator = self.filter_tag.operator(); let symbol = filter_operator.statement(); - let ty = get_scalar(&self.field.ty)?; + let ty = if is_nested { get_type_name(&self.field.ty) } else { get_scalar(&self.field.ty)? }; let column_name = self.field_tag.column_name.clone(); let param = self.field_tag.parameter_label.clone(); let table_name = table_name.into(); @@ -43,7 +79,12 @@ impl QueryFilter { if filter_operator.check_exist() { Ok(format!("{conjunctive}{SPACE}{symbol}{SPACE}{table_name}.{column_name}")) } else { - Ok(format!("{conjunctive}{SPACE}{wrapped_field_name}{SPACE}{symbol}{SPACE}({SELECT}{SPACE}{ty}${param})")) + let param_stmt = if is_nested { + EDGEQL.to_string() + } else { + format!("{SELECT}{SPACE}{ty}${param}") + }; + Ok(format!("{conjunctive}{SPACE}{wrapped_field_name}{SPACE}{symbol}{SPACE}({param_stmt})")) } } @@ -75,7 +116,7 @@ impl TryFrom<(&Field, bool)> for QueryFilter { let filter_tag_builder: FilterTagBuilder = filter_tag_builder.into(); Ok(Self { - field: QueryField::try_from((field, vec![FIELD, FILTER, AND_FILTER, OR_FILTER]))?, + field: QueryField::try_from((field, vec![FIELD, FILTER, AND_FILTER, OR_FILTER, NESTED_QUERY]))?, field_tag: field_tag_builder.build(field)?, filter_tag: filter_tag_builder.build(field, first)?, }) @@ -106,7 +147,7 @@ impl TryFrom<&Field> for QueryFilters { #[derive(Debug, Clone)] pub enum FilterStatement { NoFilter, - ManyFilter(Vec), + ManyFilter(Vec), OneFilters(QueryFilters), } @@ -115,7 +156,7 @@ impl FilterStatement { pub fn get_parameter_labels(&self) -> Vec<(Ident, String)> { if let FilterStatement::ManyFilter(filters) = self { filters.iter() - .map(|filter| (filter.field.ident.clone(), filter.field_tag.parameter_label.clone())) + .map(|filter| (filter.field().ident, filter.field_tag().parameter_label)) .collect() } else { vec![] @@ -126,9 +167,19 @@ impl FilterStatement { match self { FilterStatement::NoFilter => vec![], FilterStatement::ManyFilter(filters) => { - filters.iter().map(|f| ImplBuilderField { - field: f.field.clone(), - field_cat: FieldCat::Simple(f.field_tag.parameter_label.clone()), + + filters.iter().map(|f| { + match f { + QueryFilterStatement::SimpleField(sf) => ImplBuilderField { + field: sf.field.clone(), + field_cat: FieldCat::Simple(f.field_tag().parameter_label), + }, + QueryFilterStatement::NestedQuery(nq) => ImplBuilderField { + field: nq.field.clone(), + field_cat: FieldCat::Nested, + } + } + }).collect() } FilterStatement::OneFilters(filter) => vec![ @@ -174,11 +225,9 @@ impl FilterStatement { pub fn struct_field_quote(&self) -> proc_macro2::TokenStream { match self { - FilterStatement::NoFilter => { - quote!() - } + FilterStatement::NoFilter => quote!(), FilterStatement::ManyFilter(filters) => { - let fields = filters.iter().map(|f| f.field.struct_field_quote()); + let fields = filters.iter().map(|f| f.field().struct_field_quote()); quote! { #(#fields)* } @@ -195,7 +244,17 @@ impl FilterStatement { FilterStatement::NoFilter => quote!(), FilterStatement::ManyFilter(filters) => { let shapes = filters.iter() - .map(|f| f.field.field_shape_quote(f.field_tag.parameter_label.clone())); + .map(|f| { + match f { + QueryFilterStatement::SimpleField(sf) => { + sf.field.field_shape_quote(f.field_tag().parameter_label) + } + QueryFilterStatement::NestedQuery(nq) => { + nested_element_shape(nq.field.ident.clone()) + } + } + + }); quote! { #(#shapes)* @@ -211,14 +270,24 @@ impl FilterStatement { FilterStatement::NoFilter => quote!(), FilterStatement::ManyFilter(filters) => { let shapes = filters.iter() - .map(|f| f.field.field_value_quote()); + .map(|f| { + match f { + QueryFilterStatement::SimpleField(sf) => { + sf.field.field_value_quote() + } + QueryFilterStatement::NestedQuery(nq) => { + nested_element_value(nq.field.ident.clone()) + } + } + + }); quote! { #(#shapes)* } } - FilterStatement::OneFilters(filters) => nested_element_value(filters.field.ident.clone()) + FilterStatement::OneFilters(filters) => nested_element_value(filters.field.ident.clone()), } } @@ -268,20 +337,22 @@ pub fn filters_from_fields(field_iter: Iter, exclude_tags: Vec<&str>, que Ok(stmt) } -pub fn get_query_filters(field_iter: Iter, exclude_tags: Vec<&str>, query: FilterRequiredQuery, error_msg: &str, stmt: &mut FilterStatement) -> syn::Result> { - let mut filters: Vec = vec![]; +pub fn get_query_filters(field_iter: Iter, exclude_tags: Vec<&str>, query: FilterRequiredQuery, error_msg: &str, stmt: &mut FilterStatement) -> syn::Result> { + let mut filters: Vec = vec![]; for field in field_iter { if !field.attrs.is_empty() && has_none_attribute(field, exclude_tags.clone()) && !has_attribute(field, FILTERS) { if query == FilterRequiredQuery::Update && field.attrs.len() == 1 && has_attribute(field, FIELD) { continue + } else if has_attribute(field, NESTED_QUERY) { + filters.push(QueryFilterStatement::NestedQuery(NestedQueryField::try_from((field, NestedQueryParentType::Filter(filters.is_empty())))?)); } else if has_any_attribute(field, vec![FILTER, AND_FILTER, OR_FILTER]) { if let FilterStatement::OneFilters(_) = stmt { return Err(syn::Error::new_spanned(field, EITHER_ONE_FILTERS_OR_FILTER_TAG_EXPECTED)); } - filters.push(QueryFilter::try_from((field, filters.is_empty()))?); + filters.push(QueryFilterStatement::SimpleField(QueryFilter::try_from((field, filters.is_empty()))?)); } else { return Err(syn::Error::new_spanned(field, error_msg)); } @@ -289,4 +360,16 @@ pub fn get_query_filters(field_iter: Iter, exclude_tags: Vec<&str>, query } Ok(filters) } + +pub fn set_table_name(filter_statement: &mut FilterStatement, table_name: String) { + + if let FilterStatement::ManyFilter(ref mut filters) = filter_statement { + filters.iter_mut() + .for_each(|f|{ + if let QueryFilterStatement::NestedQuery(nq) = f { + nq.set_parent_table_name(table_name.clone()) + } + }) + } +} // endregion functions diff --git a/edgedb-query-derive/src/statements/nested_query.rs b/edgedb-query-derive/src/statements/nested_query.rs index af2da48..15be7be 100644 --- a/edgedb-query-derive/src/statements/nested_query.rs +++ b/edgedb-query-derive/src/statements/nested_query.rs @@ -1,23 +1,25 @@ use std::convert::TryFrom; +use quote::quote; use syn::Field; -use crate::constants::{NESTED_QUERY, SET}; +use crate::constants::{EDGEQL, NESTED_QUERY}; use crate::queries::QueryField; -use crate::tags::{build_tags_from_field, TagBuilders, Tagged}; -use crate::tags::set_tag::{SetTag, SetTagBuilder}; +use crate::statements::filters::QueryFilter; +use crate::statements::set::UpdateSet; // region NestedQueryField #[derive(Debug, Clone)] pub struct NestedQueryField { pub field: QueryField, - pub set_tag: Option, + pub set: Option, + pub filter: Option, pub parent_table_name: Option } pub enum NestedQueryParentType { - Query, Set + Query, Set, Filter(bool) } impl TryFrom<(&Field, NestedQueryParentType)> for NestedQueryField { @@ -25,20 +27,23 @@ impl TryFrom<(&Field, NestedQueryParentType)> for NestedQueryField { fn try_from((field, parent_type): (&Field, NestedQueryParentType)) -> Result { - let (set_tag,tags) = match parent_type { + let (set, filter, query_field) = match parent_type { NestedQueryParentType::Query => { - (None, vec![NESTED_QUERY]) + (None, None, QueryField::try_from((field, vec![NESTED_QUERY]))?) } NestedQueryParentType::Set => { - let mut set_tag_builder = TagBuilders::SetBuilder(SetTagBuilder::default()); - build_tags_from_field(&Tagged::StructField(field.clone()), vec![&mut set_tag_builder])?; - let set_tag_builder: SetTagBuilder = set_tag_builder.into(); - (Some(set_tag_builder.build(field)?), vec![NESTED_QUERY, SET]) + let set = UpdateSet::try_from(field)?; + (Some(set.clone()), None, set.field) + }, + NestedQueryParentType::Filter(first) => { + let filter = QueryFilter::try_from((field, first))?; + (None, Some(filter.clone()), filter.field) } }; Ok(Self { - field: QueryField::try_from((field, tags))?, - set_tag, + field: query_field, + set, + filter, parent_table_name: None }) } @@ -51,13 +56,54 @@ impl NestedQueryField { } pub fn query_statement_quote(&self) -> proc_macro2::TokenStream { - self.field.add_stmt_quote(true, None, self.parent_table_name.clone()) + self.add_stmt_quote(true).unwrap_or_else(|e| e.to_compile_error()) } pub fn add_set_statement_quote(&self) -> proc_macro2::TokenStream { - self.field.add_stmt_quote(false, self.set_tag.clone(), self.parent_table_name.clone()) + self.add_stmt_quote(false).unwrap_or_else(|e| e.to_compile_error()) } - + + pub fn build_statement(&self) -> syn::Result { + if let Some(set) = self.set.as_ref() { + Ok(set.build_statement(true)) + } else if let Some( filter) = self.filter.as_ref() { + filter.build_statement(self.parent_table_name.clone().unwrap_or(String::default()), true) + } else { + Ok(format!("{} := ({}), ", self.field.ident, EDGEQL)) + } + } + + pub fn add_stmt_quote(&self, to_query: bool) -> syn::Result { + let f_name = self.field.ident.clone(); + + let edge_ql = EDGEQL.to_string(); + + let field_statement = self.build_statement()?; + + let parent_table_name = self.parent_table_name.clone().unwrap_or(String::default()); + + let add_quote = if to_query { + quote!(query.push_str(p.as_str());) + } else { + quote!(set_stmt.push_str(p.as_str());) + }; + + Ok(quote! { + + let mut nested_edgeql = self.#f_name.to_edgeql(); + let nested_table_name = nested_edgeql.table_name.clone(); + + if #parent_table_name == nested_table_name.as_str() { + nested_edgeql = nested_edgeql.detached(); + } + + let p = #field_statement.to_owned() + .replace(#edge_ql, nested_edgeql.to_string().as_str()); + + #add_quote + }) + } + } // endregion NestedQueryField \ No newline at end of file diff --git a/edgedb-query-derive/src/statements/set.rs b/edgedb-query-derive/src/statements/set.rs index 9a6264f..78ec72d 100644 --- a/edgedb-query-derive/src/statements/set.rs +++ b/edgedb-query-derive/src/statements/set.rs @@ -4,7 +4,7 @@ use proc_macro2::Ident; use quote::{quote, ToTokens}; use syn::Field; use syn::punctuated::Iter; -use crate::constants::{EITHER_ONE_SETS_OR_SET_TAG_EXPECTED, FIELD, INVALID_UPDATE_TAG, NESTED_QUERY, ONLY_ONE_SETS_TAG_EXPECTED, SELECT, SET, SETS}; +use crate::constants::{EDGEQL, EITHER_ONE_SETS_OR_SET_TAG_EXPECTED, FIELD, INVALID_UPDATE_TAG, NESTED_QUERY, ONLY_ONE_SETS_TAG_EXPECTED, SELECT, SET, SETS}; use crate::builders::impl_builder::{FieldCat, ImplBuilderField}; use crate::queries::{check_duplicate_parameter_labels, QueryField}; @@ -27,7 +27,7 @@ impl SetStatement { pub fn param_field(&self) -> Option<(Ident, String)> { match self { Self::SimpleField(f) => Some((f.field.ident.clone(), f.field_tag.parameter_label.clone())), - Self::NestedQuery(_) => None + Self::NestedQuery(f) => f.set.clone().map(|s| (s.field.ident.clone(), s.field_tag.parameter_label)) } } @@ -85,26 +85,31 @@ pub struct UpdateSet { pub field: QueryField, pub field_tag: FieldTag, pub set_tag: SetTag, - pub is_nested: bool, } impl UpdateSet { - pub fn build_statement(&self) -> String { + pub fn build_statement(&self, is_nested: bool) -> String { let column_name = self.field_tag.column_name.clone(); let scalar_type = self.field_tag.scalar_type.clone(); let param = self.field_tag.parameter_label.clone(); let assignment = self.set_tag.option.statement(); + let param_stmt = if is_nested { + EDGEQL.to_string() + } else { + format!("{SELECT} {scalar_type}${param}") + }; + match self.set_tag.option { - SetOption::Assign => format!("{column_name} {assignment} ({SELECT} {scalar_type}${param}), "), - SetOption::Concat => format!("{column_name} := .{column_name} ++ ({SELECT} {scalar_type}${param}), "), - SetOption::Push => format!("{column_name} += ({SELECT} {scalar_type}${param}), "), - SetOption::Remove => format!("{column_name} -= ({SELECT} {scalar_type}${param}), ") + SetOption::Assign => format!("{column_name} {assignment} ({param_stmt}), "), + SetOption::Concat => format!("{column_name} := .{column_name} {assignment} ({param_stmt}), "), + SetOption::Push => format!("{column_name} {assignment} ({param_stmt}), "), + SetOption::Remove => format!("{column_name} {assignment} ({param_stmt}), ") } } pub fn add_set_statement_quote(&self) -> proc_macro2::TokenStream { - let stmt = self.build_statement(); + let stmt = self.build_statement(false); quote! { set_stmt.push_str(#stmt); } @@ -127,7 +132,6 @@ impl TryFrom<&Field> for UpdateSet { field: QueryField::try_from((field, vec![FIELD, SET, NESTED_QUERY]))?, field_tag: field_tag_builder.build(field)?, set_tag: set_tag_builder.build(field)?, - is_nested: has_attribute(field, NESTED_QUERY), }) } } diff --git a/edgedb-query-derive/src/tags/field_tag.rs b/edgedb-query-derive/src/tags/field_tag.rs index 05f0e4c..4373019 100644 --- a/edgedb-query-derive/src/tags/field_tag.rs +++ b/edgedb-query-derive/src/tags/field_tag.rs @@ -1,11 +1,12 @@ use std::convert::TryFrom; use syn::{Field, MetaNameValue}; use syn::Lit::{Bool, Str}; -use crate::constants::{COLUMN_NAME, EXPECT_NON_EMPTY_LIT, FIELD, INVALID_FIELD_TAG, PARAM, SCALAR, LINK_PROPERTY, EXPECT_LIT_BOOL, EXPECT_LIT_STR, INF_SIGN, SUP_SIGN}; +use crate::constants::{COLUMN_NAME, EXPECT_NON_EMPTY_LIT, FIELD, INVALID_FIELD_TAG, PARAM, SCALAR, LINK_PROPERTY, EXPECT_LIT_BOOL, EXPECT_LIT_STR, INF_SIGN, SUP_SIGN, NESTED_QUERY, AT_LEAST_ONE_FIELD_ATTRIBUTE_EXPECTED}; use crate::tags::{NamedValueTagBuilder, TagBuilders}; use crate::tags::TagBuilders::FieldBuilder; use crate::tags::utils::{get_column_name, validate_link_property}; -use crate::utils::type_utils::{get_scalar, match_scalar}; +use crate::utils::attributes_utils::has_attribute; +use crate::utils::type_utils::{get_scalar, get_type_name, match_scalar}; // region FieldTag #[derive(Debug, Clone)] @@ -105,6 +106,14 @@ impl FieldTagBuilder { validate_link_property(self.column_name.clone(), self.link_property, field)?; + let all_nones = self.column_name.is_none() + && self.parameter_label.is_none() + && self.scalar_type.is_none(); + + if has_attribute(field, FIELD) && all_nones { + return Err(syn::Error::new_spanned(field, AT_LEAST_ONE_FIELD_ATTRIBUTE_EXPECTED)); + } + let scalar_type = if let Some(mut scalar) = self.scalar_type.clone() { match_scalar(&field.ty, scalar.clone())?; @@ -117,6 +126,8 @@ impl FieldTagBuilder { } scalar + } else if has_attribute(field, NESTED_QUERY) { + get_type_name(&field.ty) } else { get_scalar(&field.ty)? }; diff --git a/edgedb-query-derive/src/tags/filter_tag.rs b/edgedb-query-derive/src/tags/filter_tag.rs index 955f67f..8952aeb 100644 --- a/edgedb-query-derive/src/tags/filter_tag.rs +++ b/edgedb-query-derive/src/tags/filter_tag.rs @@ -134,7 +134,7 @@ impl FilterTagBuilder { if let Some(operator) = self.operator { let tag = FilterTag { - operator: SelectFilterOperator::try_from((&field.ty, operator.clone()))?, + operator: SelectFilterOperator::try_from((&field.ty, operator, has_attribute(field,NESTED_QUERY)))?, wrapper_fn: self.wrapper_fn, }; @@ -201,10 +201,10 @@ impl SelectFilterOperator { } } -impl TryFrom<(&Type, LitStr)> for SelectFilterOperator { +impl TryFrom<(&Type, LitStr, bool)> for SelectFilterOperator { type Error = syn::Error; - fn try_from((ty, lit): (&Type, LitStr)) -> Result { + fn try_from((ty, lit, is_nested): (&Type, LitStr, bool)) -> Result { let s = lit.value(); @@ -222,7 +222,7 @@ impl TryFrom<(&Type, LitStr)> for SelectFilterOperator { }; let check_only_accepted_type = |ty: &Type, op: &str, tty: &str| -> syn::Result<()> { - if !is_type_name(&ty, tty) { + if !is_type_name(ty, tty) && !is_nested { return Err( syn::Error::new_spanned( ty, diff --git a/edgedb-query-derive/src/tags/set_tag.rs b/edgedb-query-derive/src/tags/set_tag.rs index 6666f25..ee4b410 100644 --- a/edgedb-query-derive/src/tags/set_tag.rs +++ b/edgedb-query-derive/src/tags/set_tag.rs @@ -5,6 +5,7 @@ use syn::Lit::Str; use crate::constants::*; use crate::tags::{NamedValueTagBuilder, TagBuilders}; use crate::tags::TagBuilders::SetBuilder; +use crate::utils::attributes_utils::has_attribute; use crate::utils::type_utils::is_type_name; // region SetTag @@ -64,16 +65,16 @@ impl SetOption { } } -impl TryFrom<(&Type, LitStr)> for SetOption { +impl TryFrom<(&Type, LitStr, bool)> for SetOption { type Error = syn::Error; - fn try_from((ty, lit): (&Type, LitStr)) -> Result { + fn try_from((ty, lit, is_nested): (&Type, LitStr, bool)) -> Result { let s = lit.value(); match s.to_lowercase().as_str() { ASSIGN | ASSIGN_SIGN => Ok(SetOption::Assign), CONCAT | CONCAT_SIGN => Ok(SetOption::Concat), PUSH | PUSH_SIGN => { - if is_type_name(ty, VEC) { + if is_type_name(ty, VEC) || is_nested { Ok(SetOption::Push) } else { Err(syn::Error::new_spanned(lit, PUSH_OPTION_ONLY_FOR_VEC)) @@ -81,7 +82,7 @@ impl TryFrom<(&Type, LitStr)> for SetOption { }, REMOVE | REMOVE_SIGN => { - if is_type_name(ty, VEC) { + if is_type_name(ty, VEC) || is_nested { Ok(SetOption::Remove) } else { Err(syn::Error::new_spanned(lit, REMOVE_OPTION_ONLY_FOR_VEC)) @@ -105,7 +106,7 @@ impl SetTagBuilder { pub fn build(self, field: &Field) -> syn::Result { if let Some(SetTagOptions::Option(lit)) = self.option { Ok(SetTag { - option: SetOption::try_from((&field.ty, lit))? + option: SetOption::try_from((&field.ty, lit, has_attribute(field, NESTED_QUERY)))? }) } else { Ok(SetTag { diff --git a/edgedb-query-derive/src/update_query.rs b/edgedb-query-derive/src/update_query.rs index 771cf8f..0b56b50 100644 --- a/edgedb-query-derive/src/update_query.rs +++ b/edgedb-query-derive/src/update_query.rs @@ -1,14 +1,14 @@ - +use edgedb_query::QueryType; use quote::quote; use syn:: Ident; use syn::ItemStruct; use syn::parse::{Parse, ParseStream}; -use edgedb_query::QueryType; -use crate::constants::{AND_FILTER, EXPECTED_AT_LEAST_ONE_SET_FIELD, FILTER, FILTERS, INVALID_UPDATE_TAG, NESTED_QUERY, OR_FILTER, SET, SETS}; -use crate::meta_data::{QueryMetaData, try_get_meta}; + use crate::builders::impl_builder::QueryImplBuilder; +use crate::constants::{AND_FILTER, EXPECTED_AT_LEAST_ONE_SET_FIELD, FILTER, FILTERS, INVALID_UPDATE_TAG, OR_FILTER, SET, SETS}; +use crate::meta_data::{QueryMetaData, try_get_meta}; use crate::queries::Query; -use crate::statements::filters::{FilterRequiredQuery, filters_from_fields, FilterStatement}; +use crate::statements::filters::{FilterRequiredQuery, filters_from_fields, FilterStatement, set_table_name}; use crate::statements::set::{sets_from_fields, UpdateSetStatement}; pub struct UpdateQuery { @@ -31,6 +31,7 @@ impl UpdateQuery { pub fn with_meta(&mut self, meta: QueryMetaData) -> &mut Self { self.meta = Some(meta.clone()); self.set_statement.set_parent_table_name(meta.table_name()); + set_table_name(&mut self.filter_statement, meta.table_name()); self } } @@ -100,7 +101,7 @@ impl Parse for UpdateQuery { let field_iter = strukt.fields.iter(); - query.filter_statement = filters_from_fields(field_iter.clone(), vec![SET, SETS, NESTED_QUERY], FilterRequiredQuery::Update, INVALID_UPDATE_TAG)?; + query.filter_statement = filters_from_fields(field_iter.clone(), vec![SET, SETS], FilterRequiredQuery::Update, INVALID_UPDATE_TAG)?; query.set_statement = sets_from_fields(field_iter, vec![FILTER, FILTERS, AND_FILTER, OR_FILTER], false,INVALID_UPDATE_TAG)?; diff --git a/edgedb-query-derive/src/utils/derive_utils.rs b/edgedb-query-derive/src/utils/derive_utils.rs index 6bd6c25..3e8cafa 100644 --- a/edgedb-query-derive/src/utils/derive_utils.rs +++ b/edgedb-query-derive/src/utils/derive_utils.rs @@ -53,6 +53,7 @@ pub fn element_value(f_name: Ident, field_is_option: bool) -> TokenStream { pub fn nested_element_shape(f_ident: Ident) -> TokenStream { quote! { + match self.#f_ident.to_edge_value() { edgedb_protocol::value::Value::Object { shape, fields } => { let elements = &shape.elements; diff --git a/edgedb-query-derive/test/delete_query/mod.rs b/edgedb-query-derive/test/delete_query/mod.rs index ca4dc27..a0d3664 100644 --- a/edgedb-query-derive/test/delete_query/mod.rs +++ b/edgedb-query-derive/test/delete_query/mod.rs @@ -11,9 +11,7 @@ mod delete { #[test] pub fn delete_users_test() { let del_users = DeleteUsers{}; - let edge_query: EdgeQuery = del_users.to_edge_query(); - assert_eq!(edge_query.query, "delete users::User"); } diff --git a/edgedb-query-derive/test/edge_filter/mod.rs b/edgedb-query-derive/test/edge_filter/mod.rs index 6ac1900..011ecbb 100644 --- a/edgedb-query-derive/test/edge_filter/mod.rs +++ b/edgedb-query-derive/test/edge_filter/mod.rs @@ -1,6 +1,6 @@ mod filter { - use edgedb_query_derive::{edgedb_filters}; + use edgedb_query_derive::{edgedb_filters, select_query}; use edgedb_query::{queries::filter::Filter}; use edgedb_protocol::value::Value; @@ -11,30 +11,44 @@ mod filter { #[filter(operator="=", wrapper_fn="str_lower")] pub name: String, #[and_filter(operator=">=")] - pub age: i8 + pub age: i8, + #[nested_query] + #[and_filter(operator="in")] + pub friends: FindFriends } + #[select_query(table="Person")] + pub struct FindFriends { + #[filter(operator="like")] + pub name: String + } + + #[test] pub fn test_filter() { let filter = MyFilter { name: "Joe".to_string(), - age: 18 + age: 18, + friends: FindFriends { + name: "%_ami".to_string() + } }; let query = filter.to_edgeql("users::User"); let value: Value = filter.to_edge_value(); - assert_eq!(query, "filter str_lower(users::User.identity.first_name) = (select $first_name) and users::User.age >= (select $age)"); + assert_eq!(query, "filter str_lower(users::User.identity.first_name) = (select $first_name) and users::User.age >= (select $age) and .friends in (select default::Person filter default::Person.name like (select $name))"); if let Value::Object { shape, fields } = value { - crate::test_utils::check_shape(&shape, vec!["first_name", "age"]); + crate::test_utils::check_shape(&shape, vec!["first_name", "age", "name"]); assert_eq!(fields, vec![ Some(Value::Str(filter.name)), - Some(Value::Int16(filter.age as i16)) + Some(Value::Int16(filter.age as i16)), + Some(Value::Str(filter.friends.name)) ]) } else { - assert!(false) + unreachable!() } } diff --git a/edgedb-query-derive/test/edge_set/mod.rs b/edgedb-query-derive/test/edge_set/mod.rs index b4166ae..5bf8d2b 100644 --- a/edgedb-query-derive/test/edge_set/mod.rs +++ b/edgedb-query-derive/test/edge_set/mod.rs @@ -16,6 +16,8 @@ mod tests { #[field(scalar="default::State")] pub status: Status, #[nested_query] + #[set(option=":=")] + #[field(column_name="u")] pub users: FindUsers } @@ -40,7 +42,7 @@ mod tests { users: FindUsers { name: "Joe".to_owned() } }; - assert_eq!("set { first_name := .first_name ++ (select $user_name), status := (select $status), users := (select users::User filter users::User.name = (select $name)) }", set.to_edgeql().to_string()); + assert_eq!("set { first_name := .first_name ++ (select $user_name), status := (select $status), u := (select users::User filter users::User.name = (select $name)) }", set.to_edgeql().to_string()); if let Value::Object { shape, fields} = set.to_edge_value() { diff --git a/edgedb-query-derive/test/query/add_user.edgeql b/edgedb-query-derive/test/query/add_user.edgeql new file mode 100644 index 0000000..e69de29 diff --git a/edgedb-query-derive/test/query/mod.rs b/edgedb-query-derive/test/query/mod.rs new file mode 100644 index 0000000..e5e9f5b --- /dev/null +++ b/edgedb-query-derive/test/query/mod.rs @@ -0,0 +1,74 @@ +#[cfg(test)] +pub mod query_value { + use edgedb_protocol::value::Value; + use edgedb_query::EdgeQuery; + use edgedb_query_derive::{ query }; + use edgedb_query::models::edge_query::ToEdgeQuery; + + #[query(value=r#" + insert users::User { + name := $user_name, + age := $age, + friend := ( + select users::User { + name, + age, + } + filter .name = $friend_name + ) + } + "#)] + pub struct AddUser { + #[param("user_name")] + pub name: String, + pub age: i8, + #[param("friend_name")] + pub friend: String, + } + + #[test] + fn test() { + let user = AddUser { + name: "Joe".to_string(), + age: 35, + friend: "John".to_string(), + }; + + + let expected = r#" + insert users::User { + name := $user_name, + age := $age, + friend := ( + select users::User { + name, + age, + } + filter .name = $friend_name + ) + } + "#; + + let query: EdgeQuery = user.to_edge_query(); + + assert_eq!(query.query.replace(' ', ""), expected.replace(' ', "")); + + if let Some(Value::Object { shape, fields }) = query.args { + crate::test_utils::check_shape( + &shape, + vec!["user_name", "age", "friend_name"], + ); + + assert_eq!( + fields, + vec![ + Some(Value::Str(user.name)), + Some(Value::Int16(user.age as i16)), + Some(Value::Str(user.friend)), + ] + ); + } else { + assert!(false) + } + } +} \ No newline at end of file diff --git a/edgedb-query-derive/test/select_query/mod.rs b/edgedb-query-derive/test/select_query/mod.rs index 6f14e0a..9d1689e 100644 --- a/edgedb-query-derive/test/select_query/mod.rs +++ b/edgedb-query-derive/test/select_query/mod.rs @@ -28,11 +28,9 @@ mod select { assert_eq!(edge_query.query, expected_query); - if let Some(Value::Nothing) = edge_query.args { - assert!(true) - } else { - assert!(false) - } + let Some(Value::Nothing) = edge_query.args else { + unreachable!() + }; } #[select_query(module = "users", table = "User", result = "UserResult")] @@ -41,11 +39,12 @@ mod select { pub name: (), } + #[test] pub fn filter_exists_test() { let q = FindUsersByNameExists { - name: (), + name: () }; let edge_query : EdgeQuery = q.to_edge_query(); @@ -187,7 +186,7 @@ mod select { crate::test_utils::check_shape(&shape, query_args); assert_eq!(fields, args_values) } else { - assert!(false) + unreachable!() } } @@ -221,7 +220,7 @@ mod select { Some(Value::Int16(q.age as i16)) ]) } else { - assert!(false) + unreachable!() } } @@ -262,7 +261,7 @@ mod select { Some(Value::Int16(q.age as i16)) ]) } else { - assert!(false) + unreachable!() } } @@ -310,7 +309,7 @@ mod select { Some(Value::Int16(q.filters.age as i16)) ]) } else { - assert!(false) + unreachable!() } } diff --git a/edgedb-query-derive/test/tests.rs b/edgedb-query-derive/test/tests.rs index ff89e37..d104698 100644 --- a/edgedb-query-derive/test/tests.rs +++ b/edgedb-query-derive/test/tests.rs @@ -6,4 +6,5 @@ mod edge_filter; mod delete_query; mod edge_set; mod update_query; -mod from_file_query; \ No newline at end of file +mod from_file_query; +mod query; \ No newline at end of file diff --git a/edgedb-query-derive/test/update_query/mod.rs b/edgedb-query-derive/test/update_query/mod.rs index 5777b52..59a67c1 100644 --- a/edgedb-query-derive/test/update_query/mod.rs +++ b/edgedb-query-derive/test/update_query/mod.rs @@ -1,7 +1,7 @@ #[cfg(test)] mod update { use edgedb_protocol::value::Value; - use edgedb_query_derive::{edgedb_filters, edgedb_sets, query_result, select_query, update_query}; + use edgedb_query_derive::{edgedb_filters, edgedb_sets, query, query_result, update_query}; use edgedb_query::models::edge_query::ToEdgeQuery; use edgedb_query::queries::set::Sets; use uuid::Uuid; @@ -20,9 +20,9 @@ mod update { pub friends: FindUser } - #[select_query(module="users", table="User")] + #[query(value="select detached users::User filter users::User.surname = (select $surname)")] pub struct FindUser { - #[filter(operator="Is")] + #[param("surname")] pub surname: String }