diff --git a/src/data/error.rs b/src/data/error.rs new file mode 100644 index 0000000..db31387 --- /dev/null +++ b/src/data/error.rs @@ -0,0 +1,28 @@ +use core::fmt; +use serde::export::Formatter; +use std::error; + +/// Simple rust error +/// +/// @author Pierre Hubert + +#[derive(Debug, Clone)] +pub struct ExecError(pub String); + +impl ExecError { + pub fn new(msg: &str) -> ExecError { + ExecError(msg.to_string()) + } + + pub fn boxed_new(msg: &str) -> Box { + Box::new(ExecError(msg.to_string())) + } +} + +impl fmt::Display for ExecError { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write!(f, "Encountered error: {}", self.0) + } +} + +impl error::Error for ExecError {} \ No newline at end of file diff --git a/src/data/mod.rs b/src/data/mod.rs index 637785d..4f8fcdb 100644 --- a/src/data/mod.rs +++ b/src/data/mod.rs @@ -1,3 +1,5 @@ +pub mod error; pub mod config; + pub mod http_error; -pub mod http_request_handler; \ No newline at end of file +pub mod http_request_handler; diff --git a/src/helpers/database.rs b/src/helpers/database.rs index d69fb92..24583ba 100644 --- a/src/helpers/database.rs +++ b/src/helpers/database.rs @@ -1,14 +1,20 @@ +use std::collections; use std::error::Error; +use std::ops::Add; use std::sync::{Arc, Mutex}; -use mysql::Pool; +use mysql::{Binary, Pool, ResultSet}; +use mysql::prelude::Queryable; use crate::data::config::DatabaseConfig; +use crate::data::error::ExecError; /// Database access helper /// /// @author Pierre Hubert +pub type ProcessRowResult = Result>; + // Pool shared across threads static mut POOL: Option>> = None; @@ -39,4 +45,156 @@ pub fn get_connection() -> Result> { } Ok(pool.get_conn()?) +} + + +pub struct QueryInfo { + /// Fetched table + pub table: String, + + /// Query limits + pub conditions: collections::HashMap, + + /// Limit of the query (0 = no limit) + pub limit: u64, + + /// Queried arguments + pub fields: Vec, +} + +impl QueryInfo { + /// Initialize a new query on the database + pub fn new(table: &str) -> QueryInfo { + QueryInfo { + table: table.to_string(), + conditions: collections::HashMap::new(), + limit: 0, + fields: Vec::new(), + } + } + + pub fn cond(&mut self, key: &str, val: &str) { + self.conditions.insert(key.to_string(), val.to_string()); + } +} + +/// Struct used to read the result of a request +pub struct RowResult<'a> { + row: &'a mysql::Row +} + +impl<'a> RowResult<'a> { + pub fn new(row: &mysql::Row) -> RowResult { + RowResult { + row + } + } + + /// Find a column in result set + fn find_col(&self, name: &str) -> Result { + let name_bytes = name.as_bytes(); + let mut index = 0; + for c in self.row.columns_ref() { + if c.name_ref().eq(name_bytes) { + return Ok(index); + } + + index += 1; + } + + Err(ExecError(format!("Column {} not found in database result set!", name))) + } + + /// Find an integer included in the request + pub fn get_int64(&self, name: &str) -> Result { + let value: Option = self.row.get(self.find_col(name)?); + + match value { + None => Err(ExecError(format!("Could not extract integer field {} !", name))), + Some(s) => Ok(s) + } + } + + /// Find a string included in the request + pub fn get_str(&self, name: &str) -> Result { + let value: Option = self.row.get(self.find_col(name)?); + + match value { + None => Err(ExecError(format!("Could not extract string field {} !", name))), + Some(s) => Ok(s) + } + } +} + + +/// Query a single row of the database +pub fn query_row ProcessRowResult>(mut info: QueryInfo, + process_function: F) -> Result> { + info.limit = 1; + let mut list = query(info, process_function)?; + + match list.len() { + 0 => Err(ExecError::boxed_new("Request did not return a result!")), + _ => Ok(list.remove(0)) + } +} + +/// Make a simple query +pub fn query ProcessRowResult>(info: QueryInfo, process_function: F) + -> Result, Box> { + let mut params = vec![]; + + let select_elements = match info.fields.len() { + 0 => "*".to_string(), + _ => info.fields.join(", ") + }; + + // Build query + let mut query = format!("SELECT {} FROM {} ", select_elements, info.table); + + + // WHERE clause + if !info.conditions.is_empty() { + let mut where_args = vec![]; + + for (k, v) in info.conditions { + where_args.push(format!("{} = ?", k)); + params.push(v) + } + + let where_args = format!(" WHERE {} ", where_args.join(" AND ")); + query = query.add(&where_args); + } + + // LIMIT clause + if info.limit > 0 { + query = query.add(&format!(" LIMIT {}", info.limit)); + } + + // Execute query + let mut con = get_connection()?; + let stmt = con.prep(&query)?; + + let mut res = con.exec_iter(stmt, params)?; + + // This system is made to support only one dataset + let result_set = res.next_set(); + if let None = result_set { + return Err(Box::new(ExecError::new("No result set in a query!"))); + } + let result_set: ResultSet = result_set.unwrap()?; + + + // Parse each result of the dataset + let mut res: Vec = vec![]; + for row in result_set { + let row = row?; + let result = RowResult::new(&row); + + let parsed = process_function(&result)?; + res.push(parsed); + } + + + Ok(res) } \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 3c5e3ae..0a02af1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,13 @@ use comunic_server::data::config::{conf, Config}; use comunic_server::helpers::database; use comunic_server::controllers::server; +use comunic_server::helpers::database::QueryInfo; + +#[derive(Debug)] +struct User { + id : i64, + name: String, +} #[actix_rt::main] async fn main() -> std::io::Result<()> { @@ -11,6 +18,16 @@ async fn main() -> std::io::Result<()> { // Connect to the database database::connect(&conf().database).expect("Could not connect to database!"); + let mut query = QueryInfo::new("user"); + query.cond("age", "190"); + //query.cond("id", "1"); + let res = database::query_row(query, |res| Ok(User { + id: res.get_int64("id")?, + name: res.get_str("name")?, + })).unwrap(); + + println!("{:#?}", res); + // Start the server server::start_server(conf()).await }