diff --git a/canyon_crud/src/crud.rs b/canyon_crud/src/crud.rs index 018f4e86..8a1c436b 100644 --- a/canyon_crud/src/crud.rs +++ b/canyon_crud/src/crud.rs @@ -4,6 +4,8 @@ use canyon_connection::connection::DatabaseConnection; use tokio_postgres::{ToStatement, types::ToSql}; use crate::result::DatabaseResult; +use crate::query::{Query, QueryBuilder}; + @@ -36,40 +38,14 @@ pub trait Transaction { ) } } -#[async_trait] -pub trait CrudOperations: Transaction { +#[async_trait] +pub trait CrudOperations>: Transaction { /// The implementation of the most basic database usage pattern. /// Given a table name, extracts all db records for the table - /// - /// If not columns provided, performs a SELECT *, else, will query only the - /// desired columns - async fn __find_all(table_name: &str, columns: &[&str]) -> DatabaseResult { - - let sql: String = if columns.len() == 0 { // Care, conditional assignment - String::from(format!("SELECT * FROM {}", table_name)) - } else { - let mut table_columns = String::new(); - - let mut counter = 0; - while counter < columns.len() - 1 { - table_columns.push_str( - (columns.get(counter).unwrap().to_string() + ", ").as_str() - ); - counter += 1; - } - - table_columns.push_str(columns.get(counter).unwrap()); - - let query = String::from( - format!("SELECT {} FROM {}", table_columns, table_name - )); - - query - }; - - Self::query(&sql[..], &[]).await + fn __find_all(table_name: &str) -> QueryBuilder { + Query::new(format!("SELECT * FROM {}", table_name), &[]) } /// Queries the database and try to find an item on the most common pk diff --git a/canyon_crud/src/lib.rs b/canyon_crud/src/lib.rs index c5fa7c4b..e69c202c 100644 --- a/canyon_crud/src/lib.rs +++ b/canyon_crud/src/lib.rs @@ -1,3 +1,4 @@ pub mod crud; pub mod result; -pub mod mapper; \ No newline at end of file +pub mod mapper; +pub mod query; \ No newline at end of file diff --git a/canyon_crud/src/mapper.rs b/canyon_crud/src/mapper.rs index 15da676b..c8989a23 100644 --- a/canyon_crud/src/mapper.rs +++ b/canyon_crud/src/mapper.rs @@ -1,8 +1,11 @@ +use std::fmt::Debug; use tokio_postgres::Row; +use crate::crud::Transaction; + /// Sets the way of how to deserialize a custom type T /// from a Row object retrieved from a database query -pub trait RowMapper: Sized { +pub trait RowMapper>: Sized { /// Deserializes a database Row result into Self fn deserialize(row: &Row) -> T; diff --git a/canyon_crud/src/query.rs b/canyon_crud/src/query.rs new file mode 100644 index 00000000..e60c663a --- /dev/null +++ b/canyon_crud/src/query.rs @@ -0,0 +1,93 @@ +use tokio_postgres::types::ToSql; +use std::{fmt::Debug, marker::PhantomData}; + +use crate::{crud::{Transaction, CrudOperations}, result::DatabaseResult}; + +/// Holds a mut sql sentence +#[derive(Debug, Clone)] +pub struct Query<'a, T: Debug + CrudOperations + Transaction> { + sql: String, + params: &'a[Box], + marker: PhantomData +} + +impl<'a, T> Query<'a, T> where T: Debug + CrudOperations + Transaction { + pub fn new(sql: String, params: &'a[Box]) -> QueryBuilder<'a, T> { + let self_ = Self { + sql: sql, + params: params, + marker: PhantomData + }; + QueryBuilder::::new(self_) + } +} + +/// Builder for a query while chaining SQL clauses +#[derive(Debug, Clone)] +pub struct QueryBuilder<'a, T: Debug + CrudOperations + Transaction> { + query: Query<'a, T>, + where_clause: String, + and_clause: String, + // in_clause: &'a[Box], + order_by_clause: String +} +impl<'a, T: Debug + CrudOperations + Transaction> QueryBuilder<'a, T> { + + // Generates a Query object that contains the necessary data to performn a query + pub async fn query(&mut self) -> DatabaseResult { + self.query.sql.retain(|c| !r#";"#.contains(c)); + + if self.where_clause != "" { + self.query.sql.push_str(&self.where_clause) + } + if self.and_clause != "" { + self.query.sql.push_str(&self.and_clause) + } + if self.order_by_clause != "" { + self.query.sql.push_str(&self.order_by_clause) + } + // ... rest of statements + + self.query.sql.push(';'); + + + let mut unboxed_params = Vec::new(); + for element in self.query.params { + unboxed_params.push(&**element); + } + + println!("Executing query: {:?}", &self.query.sql); + + T::query(&self.query.sql[..], &unboxed_params).await + } + + pub fn new(query: Query<'a, T>) -> Self { + Self { + query: query, + where_clause: String::new(), + and_clause: String::new(), + // in_clause: &[], + order_by_clause: String::new() + } + } + + pub fn where_clause(mut self, r#where: &'a str) -> Self { + self.where_clause.push_str(&*(String::from(" WHERE ") + r#where)); + self + } + + pub fn and_clause(mut self, r#and: &'a str) -> Self { + self.and_clause.push_str(&*(String::from(" AND ") + r#and)); + self + } + + // pub fn r#in(mut self, in_values: &'a[Box]) -> Self { + // self.in_clause = in_values; + // self + // } + + pub fn order_by(mut self, order_by: &'a str) -> Self { + self.order_by_clause.push_str(&*(String::from(" ORDER BY ") + order_by)); + self + } +} \ No newline at end of file diff --git a/canyon_crud/src/result.rs b/canyon_crud/src/result.rs index 5d39a618..388ca81b 100644 --- a/canyon_crud/src/result.rs +++ b/canyon_crud/src/result.rs @@ -2,7 +2,7 @@ use std::{marker::PhantomData, fmt::Debug}; use tokio_postgres::Row; -use crate::mapper::RowMapper; +use crate::{mapper::RowMapper, crud::Transaction}; /// Represents a database result after a query, by wrapping the `tokio::postgres` result /// and providing methods to deserialize this result into a **user defined struct** @@ -24,7 +24,8 @@ impl DatabaseResult { /// Returns a Vec full filled with allocated instances of the type T. /// Z it's used to constrait the types that can call it to the same generic T type, /// and to provide a way to statically call some `Z::deserialize` method. - pub fn as_response + Debug>(&self) -> Vec { + pub fn as_response + Debug>(&self) -> Vec + where T: Transaction { let mut results = Vec::new(); diff --git a/canyon_macros/src/lib.rs b/canyon_macros/src/lib.rs index 93e275c7..fcb2942e 100755 --- a/canyon_macros/src/lib.rs +++ b/canyon_macros/src/lib.rs @@ -98,7 +98,7 @@ pub fn canyon_entity(_meta: CompilerTokenStream, input: CompilerTokenStream) -> for field in entity.attributes.iter() { let mut new_entity_field = CanyonRegisterEntityField::new(); new_entity_field.field_name = field.name.to_string(); - new_entity_field.field_type = field.get_field_type_as_string(); + new_entity_field.field_type = field.get_field_type_as_string().replace(" ", ""); new_entity.entity_fields.push(new_entity_field); } unsafe { CANYON_REGISTER_ENTITIES.push(new_entity) } @@ -203,7 +203,7 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac } impl canyon_sql::canyon_crud::mapper::RowMapper for #ty { - fn deserialize(row: &Row) -> Self { + fn deserialize(row: &Row) -> #ty { Self { #(#field_names_for_row_mapper),* } diff --git a/canyon_macros/src/query_operations/select.rs b/canyon_macros/src/query_operations/select.rs index 59da8aca..47652c52 100644 --- a/canyon_macros/src/query_operations/select.rs +++ b/canyon_macros/src/query_operations/select.rs @@ -13,18 +13,26 @@ pub fn generate_find_all_tokens(macro_data: &MacroTokens) -> TokenStream { let table_name = database_table_name_from_struct(ty); + // quote! { + // #vis async fn find_all() -> Vec<#ty> { + // <#ty as canyon_sql::canyon_crud::crud::CrudOperations<#ty>>::__find_all( + // #table_name, + // &[] + // ) + // .await + // .as_response::<#ty>() + // } + // } quote! { - #vis async fn find_all() -> Vec<#ty> { + #vis fn find_all() -> query::QueryBuilder<'static, #ty> { <#ty as canyon_sql::canyon_crud::crud::CrudOperations<#ty>>::__find_all( - #table_name, - &[] // TODO Let the user retrieves ONLY desired columns? + #table_name ) - .await - .as_response::<#ty>() } } } + /// Generates the TokenStream for build the __find_all() CRUD /// associated function pub fn generate_find_by_id_tokens(macro_data: &MacroTokens) -> TokenStream { diff --git a/canyon_observer/src/handler.rs b/canyon_observer/src/handler.rs index b757ae3f..54956862 100644 --- a/canyon_observer/src/handler.rs +++ b/canyon_observer/src/handler.rs @@ -242,7 +242,7 @@ impl DatabaseSyncOperations { pub async fn fill_operations(&mut self, data: CanyonHandler<'_>) { // For each entity (table) on the register for canyon_register_entity in data.canyon_tables { - let table_name = canyon_register_entity.entity_name.to_owned().to_lowercase(); + let table_name = canyon_register_entity.entity_name.to_owned(); println!("Current loop table \"{}\":", &table_name); // true if this table on the register is already on the database