use std::collections; use std::collections::HashMap; use std::error::Error; use std::ops::Add; use std::sync::{Arc, Mutex}; use chrono::{TimeZone, Utc}; use mysql::{Binary, Pool, ResultSet, Value}; use mysql::prelude::Queryable; use crate::data::config::{conf, DatabaseConfig}; use crate::data::conversation::ConvID; use crate::data::error::{ExecError, ResultBoxError}; use crate::data::group_id::GroupID; use crate::data::user::UserID; /// 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()?) } /// Join type #[derive(PartialEq)] pub enum DatabaseQueryJoinType { NORMAL, LEFT, } /// Structure used to implement JOIN on queries struct QueryJoin { table: String, table_alias: String, condition: String, } pub struct QueryInfo { /// Fetched table pub table: String, pub table_alias: Option, /// Joins joins_type: DatabaseQueryJoinType, joins: Vec, /// Query limits pub conditions: collections::HashMap, /// Custom WHERE condition pub custom_where: Option, /// Custom WHERE values pub custom_where_ars: Vec, /// Custom GROUP BY argument group_by: Option, /// Limit of the query (0 = no limit) pub limit: u64, /// Order of the query pub order: Option, /// Queried arguments /// /// If this attribute is empty, all the columns of the table set are fetched pub fields: Vec, } impl QueryInfo { /// Initialize a new query on the database pub fn new(table: &str) -> QueryInfo { QueryInfo { table: table.to_string(), table_alias: None, joins_type: DatabaseQueryJoinType::NORMAL, joins: Vec::new(), conditions: collections::HashMap::new(), custom_where: None, custom_where_ars: vec![], group_by: None, limit: 0, order: None, fields: Vec::new(), } } /// Main fetched table alias pub fn alias(mut self, table_alias: &str) -> QueryInfo { self.table_alias = Some(table_alias.to_string()); self } /// Set join type pub fn set_join_type(mut self, t: DatabaseQueryJoinType) -> QueryInfo { self.joins_type = t; self } pub fn join(mut self, table: &str, table_alias: &str, cond: &str) -> QueryInfo { self.joins.push(QueryJoin { table: table.to_string(), table_alias: table_alias.to_string(), condition: cond.to_string(), }); self } pub fn add_conditions(mut self, map: &HashMap) -> QueryInfo { for (k, v) in map { self.conditions.insert(k.to_string(), v.clone()); } self } pub fn cond(mut self, key: &str, val: &str) -> QueryInfo { self.conditions.insert(key.to_string(), mysql::Value::from(val)); self } pub fn cond_u32(mut self, key: &str, val: u32) -> QueryInfo { self.conditions.insert(key.to_string(), mysql::Value::from(val)); self } pub fn cond_u64(mut self, key: &str, val: u64) -> QueryInfo { self.conditions.insert(key.to_string(), mysql::Value::from(val)); self } pub fn cond_i64(mut self, key: &str, val: i64) -> QueryInfo { self.conditions.insert(key.to_string(), mysql::Value::from(val)); self } pub fn cond_user_id(mut self, key: &str, val: &UserID) -> QueryInfo { self.conditions.insert(key.to_string(), mysql::Value::from(val.id())); self } pub fn cond_group_id(mut self, key: &str, val: &GroupID) -> QueryInfo { self.conditions.insert(key.to_string(), mysql::Value::from(val.id())); self } pub fn cond_conv_id(mut self, key: &str, val: ConvID) -> QueryInfo { self.conditions.insert(key.to_string(), mysql::Value::from(val.id())); self } pub fn cond_legacy_bool(mut self, key: &str, val: bool) -> QueryInfo { let val = match val { true => 1, false => 0 }; self.conditions.insert(key.to_string(), mysql::Value::from(val)); self } /// Set custom where pub fn set_custom_where(mut self, custom_where: &str) -> QueryInfo { self.custom_where = Some(custom_where.to_string()); self } /// Add a custom u64 WHERE value pub fn add_custom_where_argument_u64(mut self, val: u64) -> QueryInfo { self.custom_where_ars.push(mysql::Value::UInt(val)); self } /// Add a custom u32 WHERE value pub fn add_custom_where_argument_u32(mut self, val: u32) -> QueryInfo { self.custom_where_ars.push(mysql::Value::UInt(val as u64)); self } /// Add a custom User ID WHERE value pub fn add_custom_where_argument_user_id(mut self, val: &UserID) -> QueryInfo { self.custom_where_ars.push(mysql::Value::UInt(val.id())); self } /// Add a custom Group ID WHERE value pub fn add_custom_where_argument_group_id(mut self, val: &GroupID) -> QueryInfo { self.custom_where_ars.push(mysql::Value::UInt(val.id())); self } /// Add a custom string WHERE value pub fn add_custom_where_argument_str(mut self, val: &str) -> QueryInfo { self.custom_where_ars.push(mysql::Value::from(val)); self } /// Append a field to the list of selected fields pub fn add_field(mut self, key: &str) -> QueryInfo { self.fields.push(key.to_string()); self } /// Set GROUP BY clause pub fn set_group_by(mut self, group_by: &str) -> QueryInfo { self.group_by = Some(group_by.to_string()); self } /// Set results ordering pub fn set_order(mut self, order: &str) -> QueryInfo { self.order = Some(order.to_string()); self } /// Set the limit for the request pub fn set_limit(mut self, value: u64) -> QueryInfo { self.limit = value; self } /// Execute query pub fn exec ProcessRowResult>(self, process_function: F) -> Result, Box> { query(self, process_function) } /// Query just a row pub fn query_row ProcessRowResult>(self, process_function: F) -> Result> { query_row(self, process_function) } /// Execute count query pub fn exec_count(self) -> ResultBoxError { count(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 an integer included in the request pub fn get_u64(&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?) } } /// Get an optional unsigned number => Set to None if value is null / empty pub fn get_optional_u64(&self, name: &str) -> ResultBoxError> { match self.is_null(name)? { true => Ok(None), false => Ok(Some(self.get_u64(name)?)) } } pub fn get_optional_u32(&self, name: &str) -> ResultBoxError> { match self.is_null(name)? { true => Ok(None), false => Ok(Some(self.get_u32(name)?)) } } /// Get an optional unsigned number => Set to None if value is null / empty / 0 pub fn get_optional_positive_u64(&self, name: &str) -> ResultBoxError> { Ok(match self.get_optional_u64(name)? { None | Some(0) => None, Some(val) => Some(val) }) } pub fn get_u32(&self, name: &str) -> ResultBoxError { 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 an integer included in the request pub fn get_usize(&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?) } } /// Get the ID of a user included in the request pub fn get_user_id(&self, name: &str) -> ResultBoxError { Ok(UserID::new(self.get_u64(name)?)) } /// Get the ID of a group included in the response pub fn get_group_id(&self, name: &str) -> ResultBoxError { Ok(GroupID::new(self.get_u64(name)?)) } /// Get the optional ID of a group included in the response pub fn get_optional_group_id(&self, name: &str) -> ResultBoxError> { Ok(match self.get_optional_u64(name)? { None | Some(0) => None, Some(id) => Some(GroupID::new(id)) }) } /// Get the ID of a conversation included in the response pub fn get_conv_id(&self, name: &str) -> ResultBoxError { Ok(ConvID::new(self.get_u64(name)?)) } /// 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) } } /// Check out whether a given value is null or empty or not pub fn is_null_or_empty(&self, name: &str) -> ResultBoxError { if self.is_null(name)? { return Ok(true); } Ok(self.get_str(name)?.is_empty()) } /// Get an optional string => Set to None if string is null / empty pub fn get_optional_str(&self, name: &str) -> ResultBoxError> { match self.is_null(name)? { true => Ok(None), false => Ok(Some(self.get_str(name)?).map_or(None, |d| { match d.is_empty() { true => None, false => Some(d) } })) } } /// Get legacy boolean value : 1 = true / 0 = false pub fn get_legacy_bool(&self, name: &str) -> ResultBoxError { Ok(self.get_int64(name)? == 1) } /// Get a MYSQL date as a timestamp pub fn get_date_as_time(&self, name: &str) -> ResultBoxError { let value = self.row.get_opt(self.find_col(name)?); let value: Value = value.ok_or(ExecError(format!("Could not find date field {} !", name)))??; // Check if it is a date if let Value::Date(year, month, day, hour, minutes, seconds, mic_secs) = value { let dt = Utc.ymd(year as i32, month as u32, day as u32) .and_hms_micro(hour as u32, minutes as u32, seconds as u32, mic_secs); return Ok(dt.timestamp() as u64); } Err(ExecError::boxed_string(format!("Field {} could not be resolved as a date !", name))) } } /// Query a single row of the database pub fn query_row ProcessRowResult>(mut info: QueryInfo, process_function: F) -> Result> { let table = info.table.clone(); info.limit = 1; let mut list = query(info, process_function)?; match list.len() { 0 => Err(ExecError::boxed_string( format!("Database query did not return a result: (table: {}) !", table))), _ => 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); // Table alias (if any) if let Some(alias) = info.table_alias { query = query.add(format!(" {} ", alias).as_str()); } // Join conditions if info.joins_type == DatabaseQueryJoinType::LEFT { query.push_str(" LEFT "); } for j in info.joins { query = query.add( format!(" JOIN {} {} ON {} ", j.table, j.table_alias, j.condition).as_ref()); } // 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.clone()); } let where_args = format!(" WHERE {} ", where_args.join(" AND ")); query = query.add(&where_args); } // Custom WHERE clause if let Some(custom_where) = info.custom_where { if !info.conditions.is_empty() { query = query.add(format!(" AND ({})", custom_where).as_str()); } else { query = query.add(format!(" WHERE ({})", custom_where).as_str()); } let mut custom_args = info.custom_where_ars; params.append(&mut custom_args); } // GROUP BY clause if let Some(group_by) = info.group_by { query.push_str(format!(" GROUP BY {} ", group_by).as_str()) } // ORDER clause if let Some(order) = info.order { query = query.add(format!(" ORDER BY {} ", order).as_str()); } // LIMIT clause if info.limit > 0 { query = query.add(&format!(" LIMIT {}", info.limit)); } // Log query, if required if conf().database.log_all_queries { watcher::ExecDatabaseQuery::new(&query, ¶ms).display() } // Execute query let mut con = get_connection()?; let stmt = con.prep(&query).or_else(|err| { println!("Error in SQL query: {}", &query); Err(err) })?; 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) } /// Count the number of results a query would have produced pub fn count(mut q: QueryInfo) -> ResultBoxError { &q.fields.clear(); &q.fields.push("COUNT(*) as count".to_string()); query(q, |res| res.get_usize("count"))?.pop() .ok_or(ExecError::boxed_new("database::count did not return a result!")) } /// Structure used to execute a insert query pub struct InsertQuery { pub table: String, pub values: HashMap, } impl InsertQuery { /// Construct a new InsertQuery instance pub fn new(table: &str) -> InsertQuery { InsertQuery { table: table.to_string(), values: HashMap::new(), } } /// Add batch values pub fn add_values(mut self, values: HashMap) -> Self { self.values.extend(values.into_iter()); self } /// Add a string pub fn add_str(mut self, key: &str, value: &str) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value)); self } /// Add an optional string. If None, an empty string will be inserted pub fn add_opt_str(mut self, key: &str, value: Option<&String>) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value.unwrap_or(&String::new()))); self } /// Add an optional number. If None, Null will be inserted pub fn add_opt_u64(mut self, key: &str, value: Option) -> InsertQuery { self.values.insert(key.to_string(), value .map(|u| Value::UInt(u)) .unwrap_or(Value::NULL)); self } /// Add an optional number. If None, Null will be inserted pub fn add_opt_u32(mut self, key: &str, value: Option) -> InsertQuery { self.values.insert(key.to_string(), value .map(|u| Value::UInt(u as u64)) .unwrap_or(Value::NULL)); self } /// Add an optional user ID. If None, Null will be inserted pub fn add_opt_user_id(self, key: &str, value: Option) -> InsertQuery { self.add_opt_u64(key, value.map(|u| u.id())) } /// Add an optional group ID. If None, Null will be inserted pub fn add_opt_group_id(self, key: &str, value: Option) -> InsertQuery { self.add_opt_u64(key, value.map(|u| u.id())) } /// Add an integer pub fn add_i64(mut self, key: &str, value: i64) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value)); self } pub fn add_u64(mut self, key: &str, value: u64) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value)); self } pub fn add_usize(mut self, key: &str, value: usize) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value)); self } pub fn add_u32(mut self, key: &str, value: u32) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value)); self } pub fn add_user_id(mut self, key: &str, value: &UserID) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value.id())); self } pub fn add_group_id(mut self, key: &str, value: &GroupID) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value.id())); self } pub fn add_conv_id(mut self, key: &str, value: ConvID) -> InsertQuery { self.values.insert(key.to_string(), Value::from(value.id())); self } /// Legacy database boolean (1 = true / 0 = false) pub fn add_legacy_bool(mut self, key: &str, value: bool) -> InsertQuery { let num = match value { true => 1, false => 0 }; self.values.insert(key.to_string(), Value::from(num)); self } /// Process insert pub fn insert(self) -> ResultBoxError> { insert(self) } /// Process insert, drop the result of the operation pub fn insert_drop_result(self) -> ResultBoxError<()> { insert(self)?; Ok(()) } /// Process insert, excepting an ID in the response pub fn insert_expect_result(self) -> ResultBoxError { let res = insert(self)?; res.ok_or(ExecError::boxed_new("Expected an ID in insert result!")) } } /// Insert a new entry into the database pub fn insert(query: InsertQuery) -> ResultBoxError> { // Collect keys let keys = query.values .keys() .map(|f| f.to_string()) .collect::>(); let query_sql = format!( "INSERT INTO {} ({}) VALUES({})", query.table, keys.join(", "), keys.iter().map(|_| "?").collect::>().join(", ") ); let mut con = get_connection()?; let res = con.exec_iter( query_sql, query.values.values().collect::>(), )?; Ok(res.last_insert_id()) } /// Structure used to build delete queries pub struct DeleteQuery { table: String, conditions: HashMap, custom_where: Option, custom_where_args: Vec, } impl DeleteQuery { /// Construct a new delete table query pub fn new(table: &str) -> DeleteQuery { DeleteQuery { table: table.to_string(), conditions: HashMap::new(), custom_where: None, custom_where_args: vec![], } } pub fn set_custom_where(mut self, cond: &str) -> Self { self.custom_where = Some(cond.to_string()); self } /// Add custom WHERE argument pub fn add_custom_where_arg_u64(mut self, arg: u64) -> Self { self.custom_where_args.push(mysql::Value::UInt(arg)); self } /// Add custom WHERE argument pub fn add_custom_where_arg_str(mut self, arg: &str) -> Self { self.custom_where_args.push(mysql::Value::from(arg)); self } /// Add batch conditions pub fn add_conditions(mut self, conditions: HashMap) -> Self { self.conditions.extend(conditions.into_iter()); self } /// Add a string condition pub fn cond_str(mut self, key: &str, value: &str) -> DeleteQuery { self.conditions.insert(key.to_string(), Value::from(value)); self } /// Add an integer condition pub fn cond_u32(mut self, key: &str, value: u32) -> DeleteQuery { self.conditions.insert(key.to_string(), Value::from(value)); self } pub fn cond_i64(mut self, key: &str, value: i64) -> DeleteQuery { self.conditions.insert(key.to_string(), Value::from(value)); self } pub fn cond_u64(mut self, key: &str, value: u64) -> DeleteQuery { self.conditions.insert(key.to_string(), Value::from(value)); self } pub fn cond_legacy_bool(mut self, key: &str, value: bool) -> DeleteQuery { let value = match value { true => 1, false => 0 }; self.conditions.insert(key.to_string(), Value::from(value)); self } pub fn cond_user_id(mut self, key: &str, value: &UserID) -> DeleteQuery { self.conditions.insert(key.to_string(), Value::from(value.id())); self } pub fn cond_group_id(mut self, key: &str, value: &GroupID) -> DeleteQuery { self.conditions.insert(key.to_string(), Value::from(value.id())); self } pub fn cond_conv_id(mut self, key: &str, value: ConvID) -> DeleteQuery { self.conditions.insert(key.to_string(), Value::from(value.id())); self } /// Execute the delete query pub fn exec(self) -> ResultBoxError<()> { delete(self) } } /// Delete an entry from the database pub fn delete(query: DeleteQuery) -> ResultBoxError<()> { if query.conditions.is_empty() && query.custom_where.is_none() { return Err(ExecError::boxed_new("DELETE without WHERE condition blocked for security reasons!")); } let mut query_sql = format!( "DELETE FROM {} WHERE {}", query.table, query.conditions.keys() .map(|f| format!("{} = ?", f)) .collect::>() .join(" AND ") ); let mut values = query.conditions.values().collect::>(); if let Some(custom_where) = &query.custom_where { if !query.conditions.is_empty() { query_sql.push_str(" AND "); } query_sql.push_str("("); query_sql.push_str(custom_where); query_sql.push_str(")"); for value in &query.custom_where_args { values.push(value); } } get_connection()?.exec_drop(query_sql, values)?; Ok(()) } pub struct UpdateInfo { table: String, cond: HashMap, custom_where: Option, custom_where_args: Vec, set: HashMap, } impl UpdateInfo { /// Prepare a new update query pub fn new(table: &str) -> UpdateInfo { UpdateInfo { table: table.to_string(), cond: HashMap::new(), custom_where: None, custom_where_args: vec![], set: HashMap::new(), } } /// Filter with a user id pub fn cond_user_id(mut self, name: &str, val: &UserID) -> UpdateInfo { self.cond.insert(name.to_string(), Value::UInt(val.id())); self } /// Filter with a group id pub fn cond_group_id(mut self, name: &str, val: &GroupID) -> UpdateInfo { self.cond.insert(name.to_string(), Value::UInt(val.id())); self } /// Filter with a conversation id pub fn cond_conv_id(mut self, name: &str, val: ConvID) -> UpdateInfo { self.cond.insert(name.to_string(), Value::UInt(val.id())); self } /// Filter with an unsigned integer pub fn cond_u64(mut self, name: &str, val: u64) -> UpdateInfo { self.cond.insert(name.to_string(), Value::UInt(val)); self } /// Filter with a legacy boolean pub fn cond_legacy_bool(mut self, name: &str, val: bool) -> UpdateInfo { let num = match val { true => 1, false => 0 }; self.cond.insert(name.to_string(), Value::Int(num)); self } /// Add custom WHERE clause pub fn custom_where(mut self, query: &str) -> UpdateInfo { self.custom_where = Some(query.to_string()); self } /// Add custom WHERE argument pub fn add_custom_where_arg_u64(mut self, arg: u64) -> UpdateInfo { self.custom_where_args.push(mysql::Value::UInt(arg)); self } /// Set a new string pub fn set_str(mut self, name: &str, val: &str) -> Self { self.set.insert(name.to_string(), Value::from(val)); self } /// Set an new optional string /// /// None => Empty string /// Some => The string pub fn set_opt_str(mut self, name: &str, val: Option) -> UpdateInfo { self.set.insert(name.to_string(), Value::from(val.unwrap_or(String::new()))); self } /// Set an u64 number /// /// None => 0 /// Some => The value pub fn set_opt_u64_or_zero(mut self, name: &str, val: Option) -> UpdateInfo { self.set.insert(name.to_string(), Value::from(val.unwrap_or(0))); self } /// Set a new legacy boolean pub fn set_legacy_bool(mut self, name: &str, val: bool) -> UpdateInfo { let num = match val { false => 0, true => 1, }; self.set.insert(name.to_string(), Value::Int(num)); self } /// Set a new unsigned number pub fn set_u32(mut self, name: &str, val: u32) -> UpdateInfo { self.set.insert(name.to_string(), Value::UInt(val as u64)); self } pub fn set_u64(mut self, name: &str, val: u64) -> UpdateInfo { self.set.insert(name.to_string(), Value::UInt(val)); self } /// Execute the update pub fn exec(self) -> ResultBoxError<()> { update(self) } } /// Execute an update query pub fn update(u: UpdateInfo) -> ResultBoxError<()> { if u.cond.is_empty() { Err(ExecError::boxed_new("Update without conditions blocked for security!"))?; } if u.set.is_empty() { Err(ExecError::boxed_new("Update with no change specified useless!"))?; } let mut query_sql = format!("UPDATE {} SET ", u.table); let mut values = vec![]; // Prepare updates let updates = u.set.keys() .into_iter() .map(|f| format!("{} = ?", f)) .collect::>() .join(", "); u.set.values().into_iter().for_each(|f| values.push(f)); // Prepare conditions let mut conditions = u.cond.keys() .into_iter() .map(|f| format!("{} = ?", f)) .collect::>() .join(" AND "); u.cond.values().into_iter().for_each(|f| values.push(f)); // Additional conditions if let Some(custom_where) = u.custom_where { conditions = format!("({}) AND ({})", conditions, custom_where); u.custom_where_args.iter().for_each(|f| values.push(f)) } query_sql = format!("{} {} WHERE {}", query_sql, updates, conditions); if conf().database.log_all_queries { watcher::ExecDatabaseQuery::new( &query_sql, &values.iter().map(|f| mysql::Value::from(f)).collect(), ).display(); } get_connection()?.exec_drop( query_sql, values, )?; Ok(()) } /// Private module used to watch what is going on the database mod watcher { /// Database logging & optimisation pub struct ExecDatabaseQuery { query: String, values: Vec, } impl ExecDatabaseQuery { /// Construct a new instance of this structure pub fn new(query: &str, values: &Vec) -> ExecDatabaseQuery { ExecDatabaseQuery { query: query.to_string(), values: values.clone(), } } /// Display the values stored inside this query pub fn display(&self) { println!("=================="); println!("DB: {}", self.query); println!("Arguments:"); for arg in &self.values { println!("* {:?} ", arg); } println!() } } }