1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2025-03-22 21:50:44 +00:00
comunicapiv3/src/helpers/database.rs

1070 lines
31 KiB
Rust

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<E> = Result<E, Box<dyn Error>>;
// Pool shared across threads
static mut POOL: Option<Arc<Mutex<mysql::Pool>>> = None;
/// Connect to the database
pub fn connect(conf: &DatabaseConfig) -> Result<(), Box<dyn Error>> {
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<mysql::PooledConn, Box<dyn Error>> {
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<String>,
/// Joins
joins_type: DatabaseQueryJoinType,
joins: Vec<QueryJoin>,
/// Query limits
pub conditions: collections::HashMap<String, mysql::Value>,
/// Custom WHERE condition
pub custom_where: Option<String>,
/// Custom WHERE values
pub custom_where_ars: Vec<mysql::Value>,
/// Custom GROUP BY argument
group_by: Option<String>,
/// Limit of the query (0 = no limit)
pub limit: u64,
/// Order of the query
pub order: Option<String>,
/// Queried arguments
///
/// If this attribute is empty, all the columns of the table set are fetched
pub fields: Vec<String>,
}
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<String, mysql::Value>) -> 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<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(self, process_function: F)
-> Result<Vec<E>, Box<dyn Error>> {
query(self, process_function)
}
/// Query just a row
pub fn query_row<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(self, process_function: F)
-> Result<E, Box<dyn Error>> {
query_row(self, process_function)
}
/// Execute count query
pub fn exec_count(self) -> ResultBoxError<usize> {
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<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, Box<dyn Error>> {
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<u64, Box<dyn Error>> {
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<Option<u64>> {
match self.is_null(name)? {
true => Ok(None),
false => Ok(Some(self.get_u64(name)?))
}
}
pub fn get_optional_u32(&self, name: &str) -> ResultBoxError<Option<u32>> {
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<Option<u64>> {
Ok(match self.get_optional_u64(name)? {
None | Some(0) => None,
Some(val) => Some(val)
})
}
pub fn get_u32(&self, name: &str) -> ResultBoxError<u32> {
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<usize, Box<dyn Error>> {
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<UserID> {
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<GroupID> {
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<Option<GroupID>> {
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<ConvID> {
Ok(ConvID::new(self.get_u64(name)?))
}
/// Find a string included in the request
pub fn get_str(&self, name: &str) -> Result<String, Box<dyn Error>> {
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<bool> {
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<bool> {
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<Option<String>> {
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<bool> {
Ok(self.get_int64(name)? == 1)
}
/// Get a MYSQL date as a timestamp
pub fn get_date_as_time(&self, name: &str) -> ResultBoxError<u64> {
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<E, F: Fn(&RowResult) -> ProcessRowResult<E>>(mut info: QueryInfo,
process_function: F) -> Result<E, Box<dyn Error>> {
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<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);
// 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, &params).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<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)
}
/// Count the number of results a query would have produced
pub fn count(mut q: QueryInfo) -> ResultBoxError<usize> {
&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<String, mysql::Value>,
}
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<String, mysql::Value>) -> 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<u64>) -> 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<u32>) -> 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<UserID>) -> 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<GroupID>) -> 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<Option<u64>> {
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<u64> {
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<Option<u64>> {
// Collect keys
let keys = query.values
.keys()
.map(|f| f.to_string())
.collect::<Vec<String>>();
let query_sql = format!(
"INSERT INTO {} ({}) VALUES({})",
query.table,
keys.join(", "),
keys.iter().map(|_| "?").collect::<Vec<&str>>().join(", ")
);
let mut con = get_connection()?;
let res = con.exec_iter(
query_sql,
query.values.values().collect::<Vec<&mysql::Value>>(),
)?;
Ok(res.last_insert_id())
}
/// Structure used to build delete queries
pub struct DeleteQuery {
table: String,
conditions: HashMap<String, Value>,
custom_where: Option<String>,
custom_where_args: Vec<mysql::Value>,
}
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<String, mysql::Value>) -> 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::<Vec<String>>()
.join(" AND ")
);
let mut values = query.conditions.values().collect::<Vec<&Value>>();
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<String, Value>,
custom_where: Option<String>,
custom_where_args: Vec<mysql::Value>,
set: HashMap<String, Value>,
}
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<String>) -> 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<u64>) -> 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::<Vec<String>>()
.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::<Vec<String>>()
.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<mysql::Value>,
}
impl ExecDatabaseQuery {
/// Construct a new instance of this structure
pub fn new(query: &str, values: &Vec<mysql::Value>) -> 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!()
}
}
}