stringqb/src/query_builder.rs

670 lines
18 KiB
Rust

//! Query Builder
//!
//! The QueryBuilder creates sql queries from chained methods
use std::any::Any;
use std::collections::HashMap;
use crate::drivers::{DatabaseDriver, DefaultDriver};
use crate::split_map_join;
/// The Wild type is any type, until examined
pub type Wild = Box<dyn Any>;
/// The position of the wildcard(s)
/// for a `like` clause
#[derive(Debug)]
pub enum LikeWildcard {
/// Wildcard before search term
/// eg. `%foo`
Before,
/// Wildcard after the search term
/// eg. `foo%`
After,
/// Wildcards surrounding the search term
/// eg. `%foo%`
Both,
}
/// The type of SQL join
#[derive(Debug)]
pub enum JoinType {
/// A `CROSS` join
Cross,
/// An `INNER` join
Inner,
/// An `OUTER` join
Outer,
/// A `LEFT (OUTER)` join
Left,
/// A `RIGHT (OUTER)` join
Right,
}
/// The sort direction
#[derive(Debug, PartialEq)]
pub enum OrderDirection {
/// Sort Ascending
Asc,
/// Sort Descending
Desc,
/// Random Sort (Not yet implemented!)
Rand,
}
#[derive(Debug)]
enum QueryClauseType {
GroupEnd,
GroupStart,
Join,
Like,
Where,
WhereIn,
}
#[derive(Debug)]
enum QueryType {
Select,
Insert,
Update,
Delete,
}
#[derive(Debug)]
struct QueryClause {
clause_type: QueryClauseType,
conjunction: String,
string: String,
}
impl QueryClause {
pub fn new(clause_type: QueryClauseType, conjunction: &str, string: &str) -> Self {
QueryClause {
clause_type,
conjunction: conjunction.to_string(),
string: string.to_string(),
}
}
}
#[derive(Debug)]
struct QueryState {
select_string: String,
from_string: String,
set_string: String,
order_string: String,
group_string: String,
// Keys for insert/update statement
set_array_keys: Vec<String>,
// Order by clause
order_array: HashMap<String, String>,
// Group by clause
group_array: Vec<String>,
// Values to apply to prepared statements
values: Vec<Wild>,
// Values to apply to where clauses in prepared statements
where_values: Vec<Wild>,
limit: Option<usize>,
offset: Option<usize>,
// Query components for complex selects
query_map: Vec<QueryClause>,
// Query components for having clauses
having_map: Vec<QueryClause>,
}
impl Default for QueryState {
fn default() -> Self {
QueryState {
select_string: String::from(""),
from_string: String::from(""),
set_string: String::from(""),
order_string: String::from(""),
group_string: String::from(""),
set_array_keys: vec![],
order_array: HashMap::new(),
group_array: vec![],
values: vec![],
where_values: vec![],
limit: None,
offset: None,
query_map: vec![],
having_map: vec![],
}
}
}
impl QueryState {
pub fn new() -> Self {
QueryState::default()
}
pub fn append_select_string(&mut self, s: &str) -> &mut Self {
self.select_string += s;
self
}
pub fn append_where_values(&mut self, val: Wild) -> &mut Self {
self.where_values.push(val);
self
}
pub fn append_query_map(
&mut self,
clause_type: QueryClauseType,
conj: &str,
s: &str,
) -> &mut Self {
self.query_map.push(QueryClause::new(clause_type, conj, s));
self
}
pub fn set_from_string(&mut self, s: &str) -> &mut Self {
self.from_string = s.to_owned();
self
}
}
/// The struct representing a query builder
#[derive(Debug)]
pub struct QueryBuilder {
state: QueryState,
driver: Box<dyn DatabaseDriver>,
}
impl Default for QueryBuilder {
/// Creates a new QueryBuilder instance with default driver
fn default() -> Self {
QueryBuilder {
state: QueryState::new(),
driver: Box::new(DefaultDriver::new()),
}
}
}
impl QueryBuilder {
/// Create a new QueryBuilder instance with a driver
pub fn new(driver: impl DatabaseDriver + 'static) -> Self {
QueryBuilder {
state: QueryState::new(),
driver: Box::new(driver),
}
}
// --------------------------------------------------------------------------
// ! Select Queries
// --------------------------------------------------------------------------
/// Set the fields to select from the database as a string
pub fn select(&mut self, fields: &str) -> &mut Self {
let fields = split_map_join(fields, ",", |s| s.trim().to_string());
// Split identifiers on `As` keyword so they can be quoted properly
// @TODO split identifiers on `as` keyword (needs to be case-insensitive)
// Quote the identifiers (where there was an `as` keyword)
// Rejoin those identifiers
self.state.append_select_string(&fields);
self
}
/// Set the fields to select from the database as a Vector
pub fn select_vec(&mut self, fields: Vec<&str>) -> &mut Self {
let fields = fields.join(",");
self.select(&fields)
}
/// Adds the `distinct` keyword to a query
pub fn distinct(&mut self) -> &mut Self {
unimplemented!();
}
/// Specify the database table to select from
pub fn from(&mut self, table_name: &str) -> &mut Self {
// @TODO properly escape the table name
self.state.from_string = table_name.to_string();
self
}
// --------------------------------------------------------------------------
// ! 'Like' methods
// --------------------------------------------------------------------------
/// Creates a `like` clause in the sql statement
pub fn like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
self._like(field, value, position, "LIKE", "AND")
}
/// Generates an OR Like clause
pub fn or_like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
self._like(field, value, position, "LIKE", "OR")
}
/// Generates a NOI Like clause
pub fn not_like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
self._like(field, value, position, "NOT LIKE", "AND")
}
/// Generates an OR NOT Like clause
pub fn or_not_like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
self._like(field, value, position, "NOT LIKE", "OR")
}
// --------------------------------------------------------------------------
// ! Having methods
// --------------------------------------------------------------------------
/// Add a `having` clause to the query
pub fn having(&mut self, key: &str, value: Wild) -> &mut Self {
unimplemented!();
}
/// Add a `having` clause to the query, prefixed with an `or`
pub fn or_having(&mut self, key: &str, value: Wild) -> &mut Self {
unimplemented!();
}
// --------------------------------------------------------------------------
// ! 'Where' methods
// --------------------------------------------------------------------------
/// Specify a condition for the `where` clause of the query
pub fn r#where(&mut self, key: &str, op: &str, value: Wild) -> &mut Self {
// @TODO actually implement setting the keys for the where
self.state.where_values.push(value);
self
}
/// Specify a condition for a `where` clause where a column has a value
pub fn where_eq(&mut self, key: &str, value: Wild) -> &mut Self {
self.r#where(key, "=", value)
}
/// Specify a condition for the `where` clause of the query, prefixed with `or`
pub fn or_where(&mut self, key: &str, value: Wild) -> &mut Self {
unimplemented!();
}
/// Specify a `where in` clause for the query
pub fn where_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
/// Specify a `where in` clause for the query, prefixed with `or`
pub fn or_where_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
/// Specify a `where not in` clause for the query
pub fn where_not_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
/// Specify a `where not in` clause for the query, prefixed with `or`
pub fn or_where_not_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
// --------------------------------------------------------------------------
// ! Other Query Modifier methods
// --------------------------------------------------------------------------
/// Set a key and value for an insert or update query
pub fn set(&mut self, key: &str, value: Wild) -> &mut Self {
// @TODO figure a way to make this easier to use
self.state.set_array_keys.push(key.to_string());
self.state.values.push(value);
self
}
/// Set a map of data for an insert or update query
pub fn set_map(&mut self, data: HashMap<String, Wild>) -> &mut Self {
for (key, value) in data {
self.set(&key, value);
}
self
}
/// Convenience method for a `left` join
pub fn left_join(&mut self, table: &str, col: &str, op: &str, value: &str) -> &mut Self {
self.join(table, col, op, value, JoinType::Left)
}
/// Convenience method for an `inner` join
pub fn inner_join(&mut self, table: &str, col: &str, op: &str, value: &str) -> &mut Self {
self.join(table, col, op, value, JoinType::Inner)
}
/// Add a table join to the query
pub fn join(
&mut self,
table: &str,
col: &str,
op: &str,
value: &str,
join_type: JoinType,
) -> &mut Self {
let table = self.driver.quote_identifier(table);
let col = self.driver.quote_identifier(col);
let condition = table + " ON " + &col + op + value;
let join_type = match join_type {
JoinType::Cross => "CROSS ",
JoinType::Left => "LEFT ",
JoinType::Inner => "INNER ",
JoinType::Outer => "OUTER ",
JoinType::Right => "RIGHT ",
};
let conjunction = "\n".to_string() + join_type + "JOIN ";
self.state
.append_query_map(QueryClauseType::Join, &conjunction, &condition);
self
}
/// Add a group by clause to the query
pub fn group_by(&mut self, field: &str) -> &mut Self {
self.state.group_array.push(field.to_string());
self.state.group_string = " GROUP BY ".to_string() + &self.state.group_array.join(",");
self
}
/// Add an order by clause to the query
pub fn order_by(&mut self, field: &str, direction: OrderDirection) -> &mut Self {
if direction == OrderDirection::Rand {
// @TODO handle random sorting
unimplemented!();
}
let field = self.driver.quote_identifier(field);
let dir = match direction {
OrderDirection::Asc => String::from("ASC"),
OrderDirection::Desc => String::from("DESC"),
OrderDirection::Rand => String::from("RAND"),
};
self.state.order_array.insert(field, dir);
let mut order_clauses: Vec<String> = vec![];
for (f, dir) in &self.state.order_array {
let clause = String::clone(f) + " " + &dir;
&order_clauses.push(clause);
}
let order_str = if direction != OrderDirection::Rand {
"\nORDER BY ".to_string() + &order_clauses.join(", ")
} else {
unimplemented!();
};
self.state.order_string = order_str;
self
}
/// Add a limit to the query
pub fn limit(&mut self, limit: usize) -> &mut Self {
self.state.limit = Some(limit);
self
}
/// Add an offset to the query
pub fn offset(&mut self, offset: usize) -> &mut Self {
self.state.offset = Some(offset);
self
}
// --------------------------------------------------------------------------
// ! Query Grouping Methods
// --------------------------------------------------------------------------
/// Start a logical grouping in the current query
pub fn group_start(&mut self) -> &mut Self {
let conj = if self.state.query_map.len() == 0 {
" WHERE "
} else {
" "
};
self.state
.append_query_map(QueryClauseType::GroupStart, conj, "(");
self
}
/// Start a logical grouping, prefixed with `not`
pub fn not_group_start(&mut self) -> &mut Self {
let conj = if self.state.query_map.len() == 0 {
" WHERE "
} else {
" AND "
};
self.state
.append_query_map(QueryClauseType::GroupStart, conj, "NOT (");
self
}
/// Start a logical grouping, prefixed with `or`
pub fn or_group_start(&mut self) -> &mut Self {
self.state
.append_query_map(QueryClauseType::GroupStart, "", " OR (");
self
}
/// Start a logical grouping, prefixed with `or not`
pub fn or_not_group_start(&mut self) -> &mut Self {
self.state
.append_query_map(QueryClauseType::GroupStart, "", " OR NOT (");
self
}
/// End the current logical grouping
pub fn group_end(&mut self) -> &mut Self {
self.state
.append_query_map(QueryClauseType::GroupEnd, "", ")");
self
}
// --------------------------------------------------------------------------
// ! Query execution methods
// --------------------------------------------------------------------------
/// Execute the built query
pub fn get(self) {
unimplemented!();
}
/// Count all the rows in the specified database table
pub fn count_all(self, table: &str) -> usize {
unimplemented!();
}
/// Execute the generated insert query
pub fn insert(&mut self, table: &str) {
// @TODO determine query result type
unimplemented!();
}
/// Execute the generated update query
pub fn update(&mut self, table: &str) {
// @TODO determine query result type
unimplemented!();
}
/// Execute the generated delete query
pub fn delete(&mut self, table: &str) {
unimplemented!();
}
// --------------------------------------------------------------------------
// ! SQL Returning Methods
// --------------------------------------------------------------------------
/// Get the generated SQL for a select query
pub fn get_compiled_select(self) -> String {
unimplemented!();
}
/// Get the generated SQL for an insert query
pub fn get_compiled_insert(self) -> String {
unimplemented!();
}
/// Get the generated SQL for an update query
pub fn get_compiled_update(self) -> String {
unimplemented!();
}
/// Get the generated SQL for a delete query
pub fn get_compiled_delete(self) -> String {
unimplemented!();
}
// --------------------------------------------------------------------------
// ! Miscellaneous Methods
// --------------------------------------------------------------------------
/// Get a new instance of the query builder
pub fn reset_query(mut self) -> Self {
self.state = QueryState::new();
self
}
// --------------------------------------------------------------------------
// ! Implementation Details
// --------------------------------------------------------------------------
fn _like(
&mut self,
field: &str,
value: Wild,
position: LikeWildcard,
like: &str,
conj: &str,
) -> &mut Self {
let field = self.driver.quote_identifier(field);
let like = format!("{} {} ?", field, like);
let string_val = value.downcast::<String>().unwrap();
// @TODO Properly parse types of `value` for string formatting
let value = match position {
LikeWildcard::Before => format!("%{}", *string_val),
LikeWildcard::After => format!("{}%s", *string_val),
LikeWildcard::Both => format!("%{}%", *string_val),
};
let conj = if self.state.query_map.len() == 0 {
" WHERE "
} else {
conj
};
self.state
.append_query_map(QueryClauseType::Like, conj, &like);
self.state.append_where_values(Box::new(value));
self
}
fn _where(key: &str, values: Vec<Wild>) -> HashMap<String, Wild> {
unimplemented!();
}
fn _where_in(&mut self, key: &str, values: Vec<Wild>) -> &mut Self {
unimplemented!();
}
fn _where_in_string(&mut self, key: &str, values: Vec<Wild>) -> &mut Self {
unimplemented!();
}
fn _where_string(&mut self, key: &str, value: Wild) -> &mut Self {
unimplemented!();
}
fn compile(&self, query_type: QueryType, table: &str) -> String {
unimplemented!();
}
fn compile_type(&self, query_type: QueryType, table: &str) -> String {
unimplemented!();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_key_value() {
let mut qb = QueryBuilder::default();
qb.set("foo", Box::new("bar"));
assert_eq!(qb.state.set_array_keys[0], "foo");
assert!(qb.state.values[0].is::<&str>());
// @TODO find a way to make this kind of operation much more ergonomic
assert_eq!(*qb.state.values[0].downcast_ref::<&str>().unwrap(), "bar");
}
#[test]
fn set_hashmap() {
let mut qb = QueryBuilder::default();
let mut authors: HashMap<String, Wild> = HashMap::new();
authors.insert(
String::from("Chinua Achebe"),
Box::new(String::from("Nigeria")),
);
authors.insert(
String::from("Rabindranath Tagore"),
Box::new(String::from("India")),
);
authors.insert(String::from("Anita Nair"), Box::new(String::from("India")));
qb.set_map(authors);
// assert_eq!(qb.state.set_array_keys[0], "Chinua Achebe");
assert_eq!(qb.state.set_array_keys.len(), 3);
assert_eq!(qb.state.values.len(), 3);
}
}