use crate::{
column::Column,
condition::Condition,
mysql::util::{
generate_group_by_str, generate_having_str, generate_limit_str, generate_offset_str,
generate_order_by_str, generate_where_condition_str,
},
query::QueryBuilder,
};
use std::{collections::HashMap, sync::Arc};
use log::info;
use mysql::prelude::*;
use mysql::{Error, PooledConn, Value};
use crate::table::Table;
use crate::util::{Join, JoinType};
pub fn select<T: Table + Default>(columns: Vec<Column<T>>) -> SelectQueryBuilder<T> {
SelectQueryBuilder::new(columns)
}
#[derive(Clone)]
pub struct SelectQueryBuilder<'a, T: Table + Default> {
table: Option<T>,
columns: Vec<Column<'a, T>>,
where_condition: Option<Condition<'a>>,
distinct: bool,
group_by: Option<Vec<String>>,
order_by: Option<HashMap<Vec<String>, String>>,
limit: Option<usize>,
offset: Option<usize>,
having_condition: Option<Condition<'a>>,
except_clauses: Option<Vec<SelectQueryBuilder<'a, T>>>,
union_clauses: Option<Vec<SelectQueryBuilder<'a, T>>>,
joins: Option<Vec<Join<'a>>>,
}
impl<'a, T: Table + Default> SelectQueryBuilder<'a, T> {
pub fn new(columns: Vec<Column<'a, T>>) -> Self {
SelectQueryBuilder {
table: None,
columns,
where_condition: None,
distinct: false,
group_by: None,
order_by: None,
limit: None,
offset: None,
having_condition: None,
except_clauses: None,
union_clauses: None,
joins: None,
}
}
pub fn select(mut self, columns: Vec<Column<'a, T>>) -> Self {
self.columns = columns;
self
}
pub fn distinct(mut self) -> Self {
self.distinct = true;
self
}
pub fn from(mut self, table: T) -> Self {
self.table = Some(table);
self
}
pub fn where_clause(mut self, condition: Condition<'a>) -> Self {
self.where_condition = Some(condition);
self
}
pub fn group_by(mut self, columns: Vec<String>) -> Self {
self.group_by = Some(columns);
self
}
pub fn order_by(mut self, col_and_order: HashMap<Vec<String>, String>) -> Self {
self.order_by = Some(col_and_order);
self
}
pub fn limit(mut self, count: usize) -> Self {
self.limit = Some(count);
self
}
pub fn offset(mut self, offset: usize) -> Self {
self.offset = Some(offset);
self
}
pub fn having(mut self, condition: Condition<'a>) -> Self {
self.having_condition = Some(condition);
self
}
pub fn except(mut self, other_query: SelectQueryBuilder<'a, T>) -> Self {
match self.except_clauses {
Some(ref mut clauses) => clauses.push(other_query),
None => self.except_clauses = Some(vec![other_query]),
}
self
}
pub fn union(mut self, other_query: SelectQueryBuilder<'a, T>) -> Self {
match self.union_clauses {
Some(ref mut clauses) => clauses.push(other_query),
None => self.union_clauses = Some(vec![other_query]),
}
self
}
pub fn join(
mut self,
join_type: JoinType,
table: Arc<dyn Table>,
on_condition: Condition<'a>,
) -> Self {
match self.joins {
Some(ref mut joins) => joins.push(Join::new(join_type, table, on_condition)),
None => self.joins = Some(vec![Join::new(join_type, table, on_condition)]),
}
self
}
pub fn build_query(&self) -> String {
let columns_str = self
.columns
.iter()
.map(|c| c.build())
.collect::<Vec<String>>()
.join(", ");
let table_name = self
.table
.as_ref()
.map(|t| t.get_name().to_string())
.unwrap_or("".to_string());
let join_clauses: Vec<String> = match &self.joins {
Some(joins) => joins
.iter()
.map(|join| {
let join_type_str = match join.join_type {
JoinType::Inner => "INNER JOIN",
JoinType::Left => "LEFT JOIN",
JoinType::Right => "RIGHT JOIN",
JoinType::Full => "FULL OUTER JOIN",
};
format!(
"{} {} ON {}",
join_type_str,
join.table.get_name(),
generate_where_condition_str(Some(join.on_condition.clone()))
.replace("WHERE", "")
)
})
.collect(),
None => Vec::new(),
};
let distinct_str = if self.distinct { "DISTINCT " } else { "" };
let where_condition_str = generate_where_condition_str(self.where_condition.clone());
let group_by_str = generate_group_by_str(&self.group_by);
let order_by_str = generate_order_by_str(&self.order_by);
let limit_str = generate_limit_str(self.limit);
let offset_str = generate_offset_str(self.offset);
let having_str =
generate_having_str(self.group_by.is_some(), self.having_condition.as_ref());
let join_clause = if !join_clauses.is_empty() {
join_clauses.join(" ")
} else {
String::new()
};
let mut query = format!(
"SELECT {}{} FROM {} {} {} {} {} {} {}",
distinct_str,
columns_str,
table_name,
join_clause,
where_condition_str,
group_by_str,
having_str,
order_by_str,
format!("{} {}", limit_str, offset_str),
);
if let Some(except_clauses) = &self.except_clauses {
for except_query in except_clauses {
let except_sql = except_query.build_query();
query = format!("{} EXCEPT {}", query, except_sql);
}
}
if let Some(union_clauses) = &self.union_clauses {
for union_query in union_clauses {
let union_sql = union_query.build_query();
query = format!("{} UNION {}", query, union_sql);
}
}
query
}
pub fn build(&mut self, conn: &mut PooledConn) -> Result<Vec<T>, Error> {
let final_query = self.build_query();
info!("{}", final_query);
println!("{}", final_query);
let query_set = conn.query_iter(final_query.as_str()).unwrap();
let mut results: Vec<T> = Vec::new();
for row_result in query_set {
let row = row_result.unwrap(); let mut instance = T::default();
for column in row.columns_ref() {
let column_value = &row[column.name_str().as_ref()];
let column_value_str = match column_value {
Value::NULL => "NULL".to_string(),
Value::Bytes(bytes) => String::from_utf8_lossy(bytes).to_string(),
Value::Int(i) => i.to_string(),
Value::UInt(u) => u.to_string(),
Value::Float(f) => f.to_string(),
Value::Double(d) => d.to_string(),
Value::Date(year, month, day, hour, min, sec, micro) => format!(
"{:04}-{:02}-{:02} {:02}:{:02}:{:02}.{:06}",
year, month, day, hour, min, sec, micro
),
Value::Time(neg, days, hours, minutes, seconds, micros) => format!(
"{}{:02}:{:02}:{:02}.{:06}",
if *neg { "-" } else { "" },
days * 24 + u32::from(*hours),
minutes,
seconds,
micros
),
};
instance.set_column_value(column.name_str().as_ref(), &column_value_str);
}
results.push(instance);
}
Ok(results)
}
}
impl<'a, T> QueryBuilder<'a> for SelectQueryBuilder<'a, T>
where
T: Table + Default + Clone + 'a, {
fn to_sql(&self) -> String {
self.build_query()
}
}