Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 6 additions & 30 deletions canyon_crud/src/crud.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ use canyon_connection::connection::DatabaseConnection;
use tokio_postgres::{ToStatement, types::ToSql};

use crate::result::DatabaseResult;
use crate::query::{Query, QueryBuilder};




Expand Down Expand Up @@ -36,40 +38,14 @@ pub trait Transaction<T: Debug> {
)
}
}
#[async_trait]
pub trait CrudOperations<T: Debug>: Transaction<T> {

#[async_trait]
pub trait CrudOperations<T: Debug + CrudOperations<T>>: Transaction<T> {

/// 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<T> {

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<T> {
Query::new(format!("SELECT * FROM {}", table_name), &[])
}

/// Queries the database and try to find an item on the most common pk
Expand Down
3 changes: 2 additions & 1 deletion canyon_crud/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod crud;
pub mod result;
pub mod mapper;
pub mod mapper;
pub mod query;
5 changes: 4 additions & 1 deletion canyon_crud/src/mapper.rs
Original file line number Diff line number Diff line change
@@ -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<T>: Sized {
pub trait RowMapper<T: Debug + Transaction<T>>: Sized {

/// Deserializes a database Row result into Self
fn deserialize(row: &Row) -> T;
Expand Down
93 changes: 93 additions & 0 deletions canyon_crud/src/query.rs
Original file line number Diff line number Diff line change
@@ -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<T> + Transaction<T>> {
sql: String,
params: &'a[Box<dyn ToSql + Sync>],
marker: PhantomData<T>
}

impl<'a, T> Query<'a, T> where T: Debug + CrudOperations<T> + Transaction<T> {
pub fn new(sql: String, params: &'a[Box<dyn ToSql + Sync>]) -> QueryBuilder<'a, T> {
let self_ = Self {
sql: sql,
params: params,
marker: PhantomData
};
QueryBuilder::<T>::new(self_)
}
}

/// Builder for a query while chaining SQL clauses
#[derive(Debug, Clone)]
pub struct QueryBuilder<'a, T: Debug + CrudOperations<T> + Transaction<T>> {
query: Query<'a, T>,
where_clause: String,
and_clause: String,
// in_clause: &'a[Box<dyn ToSql>],
order_by_clause: String
}
impl<'a, T: Debug + CrudOperations<T> + Transaction<T>> QueryBuilder<'a, T> {

// Generates a Query object that contains the necessary data to performn a query
pub async fn query(&mut self) -> DatabaseResult<T> {
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<dyn ToSql>]) -> 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
}
}
5 changes: 3 additions & 2 deletions canyon_crud/src/result.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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**
Expand All @@ -24,7 +24,8 @@ impl<T: Debug> DatabaseResult<T> {
/// Returns a Vec<T> 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<Z: RowMapper<T> + Debug>(&self) -> Vec<T> {
pub fn as_response<Z: RowMapper<T> + Debug>(&self) -> Vec<T>
where T: Transaction<T> {

let mut results = Vec::new();

Expand Down
4 changes: 2 additions & 2 deletions canyon_macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down Expand Up @@ -203,7 +203,7 @@ pub fn implement_row_mapper_for_type(input: proc_macro::TokenStream) -> proc_mac
}

impl canyon_sql::canyon_crud::mapper::RowMapper<Self> for #ty {
fn deserialize(row: &Row) -> Self {
fn deserialize(row: &Row) -> #ty {
Self {
#(#field_names_for_row_mapper),*
}
Expand Down
18 changes: 13 additions & 5 deletions canyon_macros/src/query_operations/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion canyon_observer/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down