use std::collections; use std::error::Error; use std::ops::Add; use std::sync::{Arc, Mutex}; use mysql::{Binary, Pool, ResultSet, Value}; use mysql::prelude::Queryable; use crate::data::config::DatabaseConfig; use crate::data::error::{ExecError, ResultBoxError}; /// Database access helper /// /// @author Pierre Hubert pub type ProcessRowResult = Result>; // Pool shared across threads static mut POOL: Option>> = None; /// Connect to the database pub fn connect(conf: &DatabaseConfig) -> Result<(), Box> { let url = format!( "mysql://{}:{}@{}:3306/{}", conf.username, conf.password, conf.host, conf.name ); let pool = Pool::new(url)?; let pool = Some(Arc::new(Mutex::new(pool))); unsafe { POOL = pool; } Ok(()) } /// Get a connection to the database pub fn get_connection() -> Result> { let pool: Pool; unsafe { let guard = POOL.as_ref().unwrap().lock(); pool = guard.unwrap().clone() } 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) -> QueryInfo { self.conditions.insert(key.to_string(), val.to_string()); self } } /// 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 = self.row.get_opt(self.find_col(name)?); match value { None => Err(ExecError::boxed_string( 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 = self.row.get_opt(self.find_col(name)?); match value { None => Err(ExecError::boxed_string( format!("Could not extract string field {} !", name))), Some(s) => Ok(s?) } } /// Check out whether a given value is null or not pub fn is_null(&self, name: &str) -> ResultBoxError { let value = self.row.get_opt(self.find_col(name)?); match value { None => Ok(true), Some(Ok(Value::NULL)) => Ok(true), _ => Ok(false) } } /// Get an optional string => Set to None if string is null pub fn get_optional_str(&self, name: &str) -> ResultBoxError> { match self.is_null(name)? { true => Ok(None), false => Ok(Some(self.get_str(name)?)) } } } /// 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) }