mirror of
				https://gitlab.com/comunic/comunicapiv3
				synced 2025-11-04 09:34:04 +00:00 
			
		
		
		
	Create query method
This commit is contained in:
		
							
								
								
									
										28
									
								
								src/data/error.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								src/data/error.rs
									
									
									
									
									
										Normal file
									
								
							@@ -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<ExecError> {
 | 
				
			||||||
 | 
					        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 {}
 | 
				
			||||||
@@ -1,3 +1,5 @@
 | 
				
			|||||||
 | 
					pub mod error;
 | 
				
			||||||
pub mod config;
 | 
					pub mod config;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
pub mod http_error;
 | 
					pub mod http_error;
 | 
				
			||||||
pub mod http_request_handler;
 | 
					pub mod http_request_handler;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,14 +1,20 @@
 | 
				
			|||||||
 | 
					use std::collections;
 | 
				
			||||||
use std::error::Error;
 | 
					use std::error::Error;
 | 
				
			||||||
 | 
					use std::ops::Add;
 | 
				
			||||||
use std::sync::{Arc, Mutex};
 | 
					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::config::DatabaseConfig;
 | 
				
			||||||
 | 
					use crate::data::error::ExecError;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// Database access helper
 | 
					/// Database access helper
 | 
				
			||||||
///
 | 
					///
 | 
				
			||||||
/// @author Pierre Hubert
 | 
					/// @author Pierre Hubert
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub type ProcessRowResult<E> = Result<E, Box<dyn Error>>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Pool shared across threads
 | 
					// Pool shared across threads
 | 
				
			||||||
static mut POOL: Option<Arc<Mutex<mysql::Pool>>> = None;
 | 
					static mut POOL: Option<Arc<Mutex<mysql::Pool>>> = None;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -39,4 +45,156 @@ pub fn get_connection() -> Result<mysql::PooledConn, Box<dyn Error>> {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    Ok(pool.get_conn()?)
 | 
					    Ok(pool.get_conn()?)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					pub struct QueryInfo {
 | 
				
			||||||
 | 
					    /// Fetched table
 | 
				
			||||||
 | 
					    pub table: String,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Query limits
 | 
				
			||||||
 | 
					    pub conditions: collections::HashMap<String, String>,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Limit of the query (0 = no limit)
 | 
				
			||||||
 | 
					    pub limit: u64,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    /// Queried arguments
 | 
				
			||||||
 | 
					    pub fields: Vec<String>,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					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<usize, ExecError> {
 | 
				
			||||||
 | 
					        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<i64, ExecError> {
 | 
				
			||||||
 | 
					        let value: Option<i64> = 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<String, ExecError> {
 | 
				
			||||||
 | 
					        let value: Option<String> = 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<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(mut info: QueryInfo,
 | 
				
			||||||
 | 
					                                                              process_function: F) -> Result<E, Box<dyn Error>> {
 | 
				
			||||||
 | 
					    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<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(info: QueryInfo, process_function: F)
 | 
				
			||||||
 | 
					                                                          -> Result<Vec<E>, Box<dyn Error>> {
 | 
				
			||||||
 | 
					    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<Binary> = result_set.unwrap()?;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Parse each result of the dataset
 | 
				
			||||||
 | 
					    let mut res: Vec<E> = vec![];
 | 
				
			||||||
 | 
					    for row in result_set {
 | 
				
			||||||
 | 
					        let row = row?;
 | 
				
			||||||
 | 
					        let result = RowResult::new(&row);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        let parsed = process_function(&result)?;
 | 
				
			||||||
 | 
					        res.push(parsed);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Ok(res)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/main.rs
									
									
									
									
									
								
							@@ -1,6 +1,13 @@
 | 
				
			|||||||
use comunic_server::data::config::{conf, Config};
 | 
					use comunic_server::data::config::{conf, Config};
 | 
				
			||||||
use comunic_server::helpers::database;
 | 
					use comunic_server::helpers::database;
 | 
				
			||||||
use comunic_server::controllers::server;
 | 
					use comunic_server::controllers::server;
 | 
				
			||||||
 | 
					use comunic_server::helpers::database::QueryInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#[derive(Debug)]
 | 
				
			||||||
 | 
					struct User {
 | 
				
			||||||
 | 
					    id : i64,
 | 
				
			||||||
 | 
					    name: String,
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#[actix_rt::main]
 | 
					#[actix_rt::main]
 | 
				
			||||||
async fn main() -> std::io::Result<()> {
 | 
					async fn main() -> std::io::Result<()> {
 | 
				
			||||||
@@ -11,6 +18,16 @@ async fn main() -> std::io::Result<()> {
 | 
				
			|||||||
    // Connect to the database
 | 
					    // Connect to the database
 | 
				
			||||||
    database::connect(&conf().database).expect("Could not connect to 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
 | 
					    // Start the server
 | 
				
			||||||
    server::start_server(conf()).await
 | 
					    server::start_server(conf()).await
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user