1
0
mirror of https://gitlab.com/comunic/comunicapiv3 synced 2025-03-26 07:30:44 +00:00
comunicapiv3/src/helpers/database.rs

304 lines
7.9 KiB
Rust
Raw Normal View History

2020-05-22 08:51:15 +02:00
use std::collections;
2020-05-21 09:21:58 +02:00
use std::error::Error;
2020-05-22 08:51:15 +02:00
use std::ops::Add;
2020-05-21 09:36:10 +02:00
use std::sync::{Arc, Mutex};
2020-05-23 14:08:22 +02:00
use mysql::{Binary, Pool, ResultSet, Value};
2020-05-22 08:51:15 +02:00
use mysql::prelude::Queryable;
2020-05-21 09:21:58 +02:00
2020-05-21 09:36:10 +02:00
use crate::data::config::DatabaseConfig;
2020-05-23 14:08:22 +02:00
use crate::data::error::{ExecError, ResultBoxError};
2020-05-24 16:35:54 +02:00
use std::collections::HashMap;
2020-05-21 09:36:10 +02:00
2020-05-21 09:21:58 +02:00
/// Database access helper
///
/// @author Pierre Hubert
2020-05-22 08:51:15 +02:00
pub type ProcessRowResult<E> = Result<E, Box<dyn Error>>;
2020-05-21 09:21:58 +02:00
// 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(())
2020-05-21 09:36:10 +02:00
}
/// 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()?)
2020-05-22 08:51:15 +02:00
}
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
2020-05-24 18:02:19 +02:00
///
/// If this attribute is empty, all the columns of the table set are fetched
2020-05-22 08:51:15 +02:00
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(),
}
}
2020-05-22 08:57:16 +02:00
pub fn cond(mut self, key: &str, val: &str) -> QueryInfo {
2020-05-22 08:51:15 +02:00
self.conditions.insert(key.to_string(), val.to_string());
2020-05-22 08:57:16 +02:00
self
2020-05-22 08:51:15 +02:00
}
2020-05-24 16:39:48 +02:00
pub fn cond_u32(mut self, key: &str, val: u32) -> QueryInfo {
self.conditions.insert(key.to_string(), val.to_string());
self
}
pub fn cond_i64(mut self, key: &str, val: i64) -> QueryInfo {
self.conditions.insert(key.to_string(), val.to_string());
self
}
2020-05-24 18:02:19 +02:00
/// 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
}
2020-05-22 08:51:15 +02:00
}
/// 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
2020-05-23 14:08:22 +02:00
pub fn get_int64(&self, name: &str) -> Result<i64, Box<dyn Error>> {
let value = self.row.get_opt(self.find_col(name)?);
2020-05-22 08:51:15 +02:00
match value {
2020-05-23 14:08:22 +02:00
None => Err(ExecError::boxed_string(
format!("Could not extract integer field {} !", name))),
Some(s) => Ok(s?)
2020-05-22 08:51:15 +02:00
}
}
/// Find a string included in the request
2020-05-23 14:08:22 +02:00
pub fn get_str(&self, name: &str) -> Result<String, Box<dyn Error>> {
let value = self.row.get_opt(self.find_col(name)?);
2020-05-22 08:51:15 +02:00
match value {
2020-05-23 14:08:22 +02:00
None => Err(ExecError::boxed_string(
format!("Could not extract string field {} !", name))),
Some(s) => Ok(s?)
2020-05-22 08:51:15 +02:00
}
}
2020-05-23 11:00:53 +02:00
2020-05-23 14:08:22 +02:00
/// 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)
}
}
2020-05-23 14:12:42 +02:00
/// Get an optional string => Set to None if string is null / empty
2020-05-23 14:08:22 +02:00
pub fn get_optional_str(&self, name: &str) -> ResultBoxError<Option<String>> {
match self.is_null(name)? {
true => Ok(None),
2020-05-23 14:12:42 +02:00
false => Ok(Some(self.get_str(name)?).map_or(None, |d|{
match d.is_empty() {
true => None,
false => Some(d)
}
}))
2020-05-23 11:00:53 +02:00
}
}
2020-05-22 08:51:15 +02:00
}
/// 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)
2020-05-24 16:35:54 +02:00
}
/// 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 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 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_u32(mut self, key: &str, value: u32) -> InsertQuery {
self.values.insert(key.to_string(), Value::from(value));
self
}
}
/// 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::<Vec<String>>();
let query_sql = format!(
"INSERT INTO {} ({}) VALUES({})",
query.table,
keys.join(", "),
keys.iter().map(|_| "?").collect::<Vec<&str>>().join(", ")
);
get_connection()?.exec_drop(
query_sql,
query.values.values().collect::<Vec<&mysql::Value>>()
)?;
Ok(())
2020-05-21 09:21:58 +02:00
}