stringqb/src/query_builder.rs

605 lines
18 KiB
Rust
Raw Normal View History

2019-04-09 18:55:53 -04:00
//! Query Builder
2019-04-02 16:35:52 -04:00
//!
//! The QueryBuilder creates sql queries from chained methods
mod query_state;
2019-04-02 14:36:11 -04:00
use std::collections::HashMap;
use crate::drivers::{DatabaseDriver, DefaultDriver};
use crate::enums::*;
2019-04-10 14:03:28 -04:00
use crate::split_map_join;
2019-04-10 20:11:26 -04:00
use crate::types::Wild;
2019-04-10 19:49:06 -04:00
2019-04-11 17:16:35 -04:00
use regex::Regex;
use query_state::QueryState;
2019-04-02 14:36:11 -04:00
2019-04-10 14:03:28 -04:00
#[derive(Debug)]
enum QueryType {
Select,
Insert,
Update,
Delete,
}
2019-04-02 16:35:52 -04:00
/// The struct representing a query builder
2019-04-09 14:13:37 -04:00
#[derive(Debug)]
2019-04-02 14:36:11 -04:00
pub struct QueryBuilder {
state: QueryState,
2019-04-09 14:13:37 -04:00
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()),
}
}
2019-04-02 14:36:11 -04:00
}
impl QueryBuilder {
2019-04-09 14:13:37 -04:00
/// Create a new QueryBuilder instance with a driver
pub fn new(driver: impl DatabaseDriver + 'static) -> Self {
2019-04-02 14:36:11 -04:00
QueryBuilder {
state: QueryState::new(),
2019-04-09 14:13:37 -04:00
driver: Box::new(driver),
2019-04-02 14:36:11 -04:00
}
}
// --------------------------------------------------------------------------
// ! Select Queries
// --------------------------------------------------------------------------
2019-04-05 14:51:31 -04:00
/// Set the fields to select from the database as a string
2019-04-11 17:27:05 -04:00
///
/// ```ignore
/// let mut qb = QueryBuilder::new(Driver::new());
///
/// // You can also alias field names
/// qb.select("foo as bar")
/// ```
2019-04-04 16:39:05 -04:00
pub fn select(&mut self, fields: &str) -> &mut Self {
2019-04-11 17:16:35 -04:00
lazy_static! {
static ref RE: Regex = Regex::new(r"(?i) as ").unwrap();
};
2019-04-10 14:03:28 -04:00
2019-04-11 17:16:35 -04:00
let fields = split_map_join(fields, ",", |s| {
if ! RE.is_match(s) {
return self.driver.quote_identifier(s.trim());
}
2019-04-10 14:03:28 -04:00
2019-04-11 17:16:35 -04:00
// Do a operation similar to split_map_join for the
// regex matches, quoting each identifier
RE.split(s)
.into_iter()
.map(|p| self.driver.quote_identifier(p))
.collect::<Vec<String>>()
.join(" as ")
});
2019-04-10 14:03:28 -04:00
self.state.append_select_string(&fields);
self
2019-04-02 17:23:52 -04:00
}
2019-04-05 20:46:07 -04:00
2019-04-05 14:51:31 -04:00
/// Set the fields to select from the database as a Vector
2019-04-11 17:27:05 -04:00
///
/// ```
/// # let mut qb = stringqb::QueryBuilder::default();
/// // You can also alias via a vector of fields
/// qb.select_vec(vec!["foo as bar", "baz"]);
/// # assert_eq!(qb.state.get_select_string(), r#""foo" as "bar","baz""#);
/// ```
2019-04-05 14:51:31 -04:00
pub fn select_vec(&mut self, fields: Vec<&str>) -> &mut Self {
let fields = fields.join(",");
self.select(&fields)
}
2019-04-02 17:23:52 -04:00
2019-04-03 16:29:51 -04:00
/// Adds the `distinct` keyword to a query
2019-04-04 16:39:05 -04:00
pub fn distinct(&mut self) -> &mut Self {
self.state.prepend_select_string(" DISTINCT");
2019-04-11 11:44:06 -04:00
self
}
/// Specify the database table to select from
2019-04-11 17:27:05 -04:00
///
/// ```ignore
/// let mut qb = QueryBuilder::new(Driver::new());
///
/// // Specifiy an alias for the table
/// qb.from("products p");
/// ```
2019-04-04 16:39:05 -04:00
pub fn from(&mut self, table_name: &str) -> &mut Self {
let from_str = split_map_join(table_name, " ", |s| self.driver.quote_identifier(s));
2019-04-11 11:44:06 -04:00
self.state.set_from_string(&from_str);
self
}
// --------------------------------------------------------------------------
// ! 'Like' methods
// --------------------------------------------------------------------------
2019-04-03 16:29:51 -04:00
/// Creates a `like` clause in the sql statement
2019-04-10 19:49:06 -04:00
pub fn like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
2019-04-10 19:37:02 -04:00
self._like(field, value, position, "LIKE", "AND")
}
2019-04-03 16:29:51 -04:00
/// Generates an OR Like clause
2019-04-10 19:49:06 -04:00
pub fn or_like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
2019-04-10 19:37:02 -04:00
self._like(field, value, position, "LIKE", "OR")
}
2019-04-03 16:29:51 -04:00
/// Generates a NOI Like clause
2019-04-10 19:49:06 -04:00
pub fn not_like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
2019-04-10 19:37:02 -04:00
self._like(field, value, position, "NOT LIKE", "AND")
}
2019-04-03 16:29:51 -04:00
/// Generates an OR NOT Like clause
2019-04-10 19:49:06 -04:00
pub fn or_not_like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self {
2019-04-10 19:37:02 -04:00
self._like(field, value, position, "NOT LIKE", "OR")
}
// --------------------------------------------------------------------------
// ! Having methods
// --------------------------------------------------------------------------
2019-04-03 16:29:51 -04:00
/// Add a `having` clause to the query
2019-04-10 19:49:06 -04:00
pub fn having(&mut self, key: &str, value: Wild) -> &mut Self {
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Add a `having` clause to the query, prefixed with an `or`
2019-04-10 19:49:06 -04:00
pub fn or_having(&mut self, key: &str, value: Wild) -> &mut Self {
unimplemented!();
}
// --------------------------------------------------------------------------
// ! 'Where' methods
// --------------------------------------------------------------------------
2019-04-03 16:29:51 -04:00
/// Specify a condition for the `where` clause of the query
2019-04-10 19:49:06 -04:00
pub fn r#where(&mut self, key: &str, op: &str, value: Wild) -> &mut Self {
2019-04-03 20:58:22 -04:00
// @TODO actually implement setting the keys for the where
self.state.append_where_values(value);
2019-04-04 16:39:05 -04:00
unimplemented!();
}
2019-04-05 20:46:07 -04:00
2019-04-10 14:03:28 -04:00
/// Specify a condition for a `where` clause where a column has a value
2019-04-10 19:49:06 -04:00
pub fn where_eq(&mut self, key: &str, value: Wild) -> &mut Self {
2019-04-05 14:51:31 -04:00
self.r#where(key, "=", value)
}
2019-04-03 16:29:51 -04:00
/// Specify a condition for the `where` clause of the query, prefixed with `or`
2019-04-10 19:49:06 -04:00
pub fn or_where(&mut self, key: &str, value: Wild) -> &mut Self {
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Specify a `where in` clause for the query
2019-04-10 19:49:06 -04:00
pub fn where_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Specify a `where in` clause for the query, prefixed with `or`
2019-04-10 19:49:06 -04:00
pub fn or_where_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Specify a `where not in` clause for the query
2019-04-10 19:49:06 -04:00
pub fn where_not_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Specify a `where not in` clause for the query, prefixed with `or`
2019-04-10 19:49:06 -04:00
pub fn or_where_not_in(&mut self, key: &str, value: Vec<Wild>) -> &mut Self {
unimplemented!();
}
// --------------------------------------------------------------------------
// ! Other Query Modifier methods
// --------------------------------------------------------------------------
2019-04-02 16:35:52 -04:00
/// Set a key and value for an insert or update query
2019-04-10 19:49:06 -04:00
pub fn set(&mut self, key: &str, value: Wild) -> &mut Self {
2019-04-02 17:23:52 -04:00
// @TODO figure a way to make this easier to use
2019-04-11 17:16:35 -04:00
let key = self.driver.quote_identifier(key);
self.state.append_set_array_keys(&key).append_values(value);
2019-04-02 14:36:11 -04:00
self
}
2019-04-02 17:23:52 -04:00
/// Set a map of data for an insert or update query
2019-04-10 19:49:06 -04:00
pub fn set_map(&mut self, data: HashMap<String, Wild>) -> &mut Self {
2019-04-02 17:23:52 -04:00
for (key, value) in data {
2019-04-04 16:39:05 -04:00
self.set(&key, value);
2019-04-02 17:23:52 -04:00
}
self
}
2019-04-09 14:13:37 -04:00
/// 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)
}
2019-04-03 16:29:51 -04:00
/// 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 {
2019-04-09 14:13:37 -04:00
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 {
2019-04-10 14:03:28 -04:00
JoinType::Cross => "CROSS ",
2019-04-09 14:13:37 -04:00
JoinType::Left => "LEFT ",
JoinType::Inner => "INNER ",
JoinType::Outer => "OUTER ",
JoinType::Right => "RIGHT ",
};
let conjunction = "\n".to_string() + join_type + "JOIN ";
2019-04-09 18:55:53 -04:00
self.state
.append_query_map(QueryClauseType::Join, &conjunction, &condition);
2019-04-09 14:13:37 -04:00
self
}
2019-04-02 17:23:52 -04:00
2019-04-03 16:29:51 -04:00
/// Add a group by clause to the query
2019-04-04 16:39:05 -04:00
pub fn group_by(&mut self, field: &str) -> &mut Self {
self.state.append_group_array(field);
let group_string = String::from(" GROUP BY ") + &self.state.get_group_array().join(",");
2019-04-09 14:13:37 -04:00
self.state.set_group_string(&group_string);
2019-04-09 14:13:37 -04:00
self
}
2019-04-03 16:29:51 -04:00
/// Add an order by clause to the query
2019-04-04 16:39:05 -04:00
pub fn order_by(&mut self, field: &str, direction: OrderDirection) -> &mut Self {
2019-04-09 14:13:37 -04:00
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.append_order_map(&field, &dir);
2019-04-09 14:13:37 -04:00
let mut order_clauses: Vec<String> = vec![];
for (f, dir) in self.state.get_order_map() {
2019-04-09 14:13:37 -04:00
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.set_order_string(&order_str);
2019-04-09 14:13:37 -04:00
self
}
2019-04-03 16:29:51 -04:00
/// Add a limit to the query
2019-04-04 16:39:05 -04:00
pub fn limit(&mut self, limit: usize) -> &mut Self {
2019-04-03 16:29:51 -04:00
self.state.limit = Some(limit);
self
}
/// Add an offset to the query
2019-04-04 16:39:05 -04:00
pub fn offset(&mut self, offset: usize) -> &mut Self {
2019-04-03 16:29:51 -04:00
self.state.offset = Some(offset);
self
}
// --------------------------------------------------------------------------
// ! Query Grouping Methods
// --------------------------------------------------------------------------
2019-04-03 16:29:51 -04:00
/// Start a logical grouping in the current query
2019-04-04 16:39:05 -04:00
pub fn group_start(&mut self) -> &mut Self {
let conj = if self.state.query_map_empty() {
2019-04-10 14:03:28 -04:00
" WHERE "
2019-04-09 14:13:37 -04:00
} else {
2019-04-10 14:03:28 -04:00
" "
};
self.state
.append_query_map(QueryClauseType::GroupStart, conj, "(");
2019-04-09 14:13:37 -04:00
self
}
2019-04-03 16:29:51 -04:00
/// Start a logical grouping, prefixed with `not`
2019-04-04 16:39:05 -04:00
pub fn not_group_start(&mut self) -> &mut Self {
let conj = if self.state.query_map_empty() {
2019-04-10 14:03:28 -04:00
" WHERE "
2019-04-09 14:13:37 -04:00
} else {
2019-04-10 14:03:28 -04:00
" AND "
};
self.state
.append_query_map(QueryClauseType::GroupStart, conj, "NOT (");
2019-04-09 14:13:37 -04:00
self
}
2019-04-03 16:29:51 -04:00
/// Start a logical grouping, prefixed with `or`
2019-04-04 16:39:05 -04:00
pub fn or_group_start(&mut self) -> &mut Self {
self.state
2019-04-09 18:55:53 -04:00
.append_query_map(QueryClauseType::GroupStart, "", " OR (");
2019-04-09 14:13:37 -04:00
self
}
2019-04-03 16:29:51 -04:00
/// Start a logical grouping, prefixed with `or not`
2019-04-04 16:39:05 -04:00
pub fn or_not_group_start(&mut self) -> &mut Self {
2019-04-09 18:55:53 -04:00
self.state
.append_query_map(QueryClauseType::GroupStart, "", " OR NOT (");
2019-04-09 14:13:37 -04:00
self
}
2019-04-03 16:29:51 -04:00
/// End the current logical grouping
2019-04-04 16:39:05 -04:00
pub fn group_end(&mut self) -> &mut Self {
self.state
2019-04-09 18:55:53 -04:00
.append_query_map(QueryClauseType::GroupEnd, "", ")");
2019-04-09 14:13:37 -04:00
self
2019-04-02 17:23:52 -04:00
}
// --------------------------------------------------------------------------
// ! Query execution methods
// --------------------------------------------------------------------------
2019-04-02 17:23:52 -04:00
/// Execute the built query
2019-04-10 14:03:28 -04:00
pub fn get(self) {
2019-04-02 17:23:52 -04:00
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Count all the rows in the specified database table
2019-04-04 16:39:05 -04:00
pub fn count_all(self, table: &str) -> usize {
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Execute the generated insert query
2019-04-04 16:39:05 -04:00
pub fn insert(&mut self, table: &str) {
// @TODO determine query result type
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Execute the generated update query
2019-04-04 16:39:05 -04:00
pub fn update(&mut self, table: &str) {
// @TODO determine query result type
unimplemented!();
}
2019-04-03 16:29:51 -04:00
/// Execute the generated delete query
2019-04-04 16:39:05 -04:00
pub fn delete(&mut self, table: &str) {
unimplemented!();
}
// --------------------------------------------------------------------------
// ! SQL Returning Methods
// --------------------------------------------------------------------------
2019-04-03 16:29:51 -04:00
/// Get the generated SQL for a select query
pub fn get_compiled_select(&self) -> String {
// The table name should already be set from the `from` method
assert!(
self.state.get_from_string().len() > 0,
"You must use the `from` method to set the table name for a select query"
);
self.compile(QueryType::Select, "")
}
2019-04-03 16:29:51 -04:00
/// Get the generated SQL for an insert query
pub fn get_compiled_insert(&self, table: &str) -> String {
self.compile(QueryType::Insert, table)
}
2019-04-03 16:29:51 -04:00
/// Get the generated SQL for an update query
pub fn get_compiled_update(&self, table: &str) -> String {
self.compile(QueryType::Update, table)
}
2019-04-03 16:29:51 -04:00
/// Get the generated SQL for a delete query
pub fn get_compiled_delete(&self, table: &str) -> String {
self.compile(QueryType::Delete, table)
}
// --------------------------------------------------------------------------
// ! Miscellaneous Methods
// --------------------------------------------------------------------------
2019-04-03 16:29:51 -04:00
/// Get a new instance of the query builder
pub fn reset(&mut self) -> &Self {
2019-04-09 14:13:37 -04:00
self.state = QueryState::new();
self
}
2019-04-09 20:45:57 -04:00
// --------------------------------------------------------------------------
// ! Implementation Details
// --------------------------------------------------------------------------
2019-04-10 19:49:06 -04:00
fn _like(
&mut self,
field: &str,
value: Wild,
position: LikeWildcard,
like: &str,
conj: &str,
) -> &mut Self {
2019-04-10 19:37:02 -04:00
let field = self.driver.quote_identifier(field);
let like = format!("{} {} ?", field, like);
2019-04-10 19:49:06 -04:00
let string_val = value.downcast::<String>().unwrap();
2019-04-10 19:37:02 -04:00
// @TODO Properly parse types of `value` for string formatting
let value = match position {
2019-04-10 19:49:06 -04:00
LikeWildcard::Before => format!("%{}", *string_val),
LikeWildcard::After => format!("{}%s", *string_val),
LikeWildcard::Both => format!("%{}%", *string_val),
2019-04-10 19:37:02 -04:00
};
let conj = if self.state.query_map_empty() {
2019-04-10 19:37:02 -04:00
" WHERE "
} else {
conj
};
2019-04-10 19:49:06 -04:00
self.state
.append_query_map(QueryClauseType::Like, conj, &like);
2019-04-10 19:37:02 -04:00
self.state.append_where_values(Box::new(value));
self
}
2019-04-10 19:49:06 -04:00
fn _where(key: &str, values: Vec<Wild>) -> HashMap<String, Wild> {
2019-04-10 19:37:02 -04:00
unimplemented!();
}
2019-04-10 19:49:06 -04:00
fn _where_in(&mut self, key: &str, values: Vec<Wild>) -> &mut Self {
2019-04-10 19:37:02 -04:00
unimplemented!();
}
2019-04-10 19:49:06 -04:00
fn _where_in_string(&mut self, key: &str, values: Vec<Wild>) -> &mut Self {
2019-04-10 19:37:02 -04:00
unimplemented!();
}
2019-04-10 19:49:06 -04:00
fn _where_string(&mut self, key: &str, value: Wild) -> &mut Self {
2019-04-10 19:37:02 -04:00
unimplemented!();
}
2019-04-10 14:03:28 -04:00
fn compile(&self, query_type: QueryType, table: &str) -> String {
// Get the base clause for the query
let base_sql = self.compile_type(query_type, &self.driver.quote_identifier(table));
let mut parts = vec![base_sql];
for clause in self.state.get_query_map() {
&parts.push(clause.to_string());
}
&parts.push(self.state.get_group_string().to_string());
&parts.push(self.state.get_order_string().to_string());
for clause in self.state.get_having_map() {
&parts.push(clause.to_string());
}
let sql = parts.join("");
// @TODO handle limit / offset
sql
2019-04-10 14:03:28 -04:00
}
fn compile_type(&self, query_type: QueryType, table: &str) -> String {
match query_type {
QueryType::Select => {
let from = self.state.get_from_string();
let select = self.state.get_select_string();
let sql = format!("SELECT *\nFROM {}", from);
if select.len() > 0 {
sql.replace("*", select)
} else {
sql
}
}
QueryType::Insert => {
let set_array_keys = self.state.get_set_array_keys();
let param_count = set_array_keys.len();
let params = vec!["?"; param_count];
format!(
"INSERT INTO {} ({})\nVALUES({})",
table,
set_array_keys.join(","),
params.join(",")
)
}
QueryType::Update => {
let set_string = self.state.get_set_string();
format!("UPDATE {}\nSET {}", table, set_string)
}
QueryType::Delete => format!("DELETE FROM {}", table),
}
2019-04-09 20:45:57 -04:00
}
2019-04-02 14:36:11 -04:00
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn set_key_value() {
2019-04-09 14:13:37 -04:00
let mut qb = QueryBuilder::default();
2019-04-04 16:39:05 -04:00
qb.set("foo", Box::new("bar"));
2019-04-02 14:36:11 -04:00
2019-04-11 17:16:35 -04:00
assert_eq!(qb.state.get_set_array_keys()[0], "\"foo\"");
assert!(qb.state.get_values()[0].is::<&str>());
2019-04-03 16:29:51 -04:00
// @TODO find a way to make this kind of operation much more ergonomic
assert_eq!(
*qb.state.get_values()[0].downcast_ref::<&str>().unwrap(),
"bar"
);
2019-04-02 14:36:11 -04:00
}
2019-04-02 17:23:52 -04:00
2019-04-03 16:29:51 -04:00
#[test]
fn set_hashmap() {
2019-04-09 14:13:37 -04:00
let mut qb = QueryBuilder::default();
2019-04-03 16:29:51 -04:00
2019-04-10 19:49:06 -04:00
let mut authors: HashMap<String, Wild> = HashMap::new();
2019-04-03 16:29:51 -04:00
authors.insert(
String::from("Chinua Achebe"),
2019-04-04 16:39:05 -04:00
Box::new(String::from("Nigeria")),
);
2019-04-03 16:29:51 -04:00
authors.insert(
String::from("Rabindranath Tagore"),
2019-04-04 16:39:05 -04:00
Box::new(String::from("India")),
);
authors.insert(String::from("Anita Nair"), Box::new(String::from("India")));
2019-04-03 16:29:51 -04:00
2019-04-04 16:39:05 -04:00
qb.set_map(authors);
2019-04-03 16:29:51 -04:00
// assert_eq!(qb.state.set_array_keys[0], "Chinua Achebe");
assert_eq!(qb.state.get_set_array_keys().len(), 3);
assert_eq!(qb.state.get_values().len(), 3);
2019-04-03 16:29:51 -04:00
}
2019-04-03 20:58:22 -04:00
}