//! 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::fns::split_map_join; use regex::Regex; /// 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, } /// The type of Query Clause #[derive(Debug, PartialEq)] enum QueryClauseType { /// Ending a parenthetical grouping GroupEnd, /// Starting a parenthetical grouping GroupStart, /// A having clause Having, /// A join clause Join, /// A like clause Like, /// A where clause Where, /// A where in clause WhereIn, } #[derive(Debug)] enum QueryType { Select, Insert, Update, Delete, } /// The struct representing a query builder pub struct QueryBuilder { /// The struct to store the query builder info state: QueryState, driver: Box, } impl Default for QueryBuilder { /// Creates a new QueryBuilder instance with default driver /// /// This is **not** useful for a real database. fn default() -> Self { QueryBuilder { state: QueryState::new(), driver: Box::new(DefaultDriver::new()), } } } impl QueryBuilder { /// Create a new QueryBuilder instance with a driver /// /// ```no_run /// use stringqb::prelude::*; /// /// // You probably do not want to use the default driver, as it /// // is basically a mock for testing /// use stringqb::drivers::DefaultDriver; /// /// // The query builder must be mutable to be useful /// let mut qb = QueryBuilder::new(DefaultDriver::new()); /// /// // Each builder method returns a mutable reference to itself so /// // the methods are chainable /// qb.select("field f").from("table"); /// /// // Since they are references, you do not have to chain. /// let sql = qb.get_compiled_select(); /// ``` 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 /// /// ```no_run /// # let mut qb = stringqb::query_builder::QueryBuilder::default(); /// // You can also alias field names /// qb.select("foo as bar"); /// ``` pub fn select(&mut self, fields: &str) -> &mut Self { lazy_static! { static ref RE: Regex = Regex::new(r"(?i) as ").unwrap(); }; let fields = split_map_join(fields, ",", |s| { if !RE.is_match(s) { return self.driver.quote_identifier(s.trim()); } // 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::>() .join(" as ") }); self.state.append_select_string(&fields); self } /// Set the fields to select from the database as a Vector /// /// ```no_run /// # let mut qb = stringqb::query_builder::QueryBuilder::default(); /// // You can also alias via a vector of fields /// qb.select_vec(vec!["foo as bar", "baz"]); /// ``` 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 { self.state.prepend_select_string(" DISTINCT"); self } /// Tell the database to give you query plain info, rather /// than a result set pub fn explain(&mut self) -> &mut Self { self.state.explain = true; self } /// Specify the database table to select from /// /// ```no_run /// # let mut qb = stringqb::query_builder::QueryBuilder::default(); /// // You can specifiy an alias for the table /// qb.from("products p"); /// ``` pub fn from(&mut self, table_name: &str) -> &mut Self { let from_str = split_map_join(table_name, " ", |s| self.driver.quote_identifier(s)); self.state.set_from_string(&from_str); self } // -------------------------------------------------------------------------- // ! 'Like' methods // -------------------------------------------------------------------------- /// Creates a `like` clause in the sql /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = stringqb::query_builder::QueryBuilder::default(); /// // Search for a value that ends with "foo" /// qb.like("field", String::from("foo"), LikeWildcard::Before); /// /// // Search for a value that starts with "foo" /// qb.like("field", String::from("foo"), LikeWildcard::After); /// /// // Search for a value that has "foo" in it /// qb.like("field", String::from("foo"), LikeWildcard::Both); /// ``` pub fn like(&mut self, field: &str, value: impl Any, 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: impl Any, position: LikeWildcard) -> &mut Self { self._like(field, value, position, "LIKE", "OR") } /// Generates a `not like` clause pub fn not_like(&mut self, field: &str, value: impl Any, 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: impl Any, position: LikeWildcard, ) -> &mut Self { self._like(field, value, position, "NOT LIKE", "OR") } // -------------------------------------------------------------------------- // ! Having methods // -------------------------------------------------------------------------- /// Add a `having` clause to the query /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // By default, key = value /// qb.having("key", vec!["value"]); /// /// // Other operators can be used with a separating space /// qb.having("clues >=", vec![4]); /// ``` pub fn having(&mut self, key: &str, value: Vec) -> &mut Self { self._having(key, value, "AND") } /// Add a `having` clause to the query, prefixed with an `or` pub fn or_having(&mut self, key: &str, value: Vec) -> &mut Self { self._having(key, value, "OR") } // -------------------------------------------------------------------------- // ! 'Where' methods // -------------------------------------------------------------------------- /// Specify a condition for the `where` clause of the query. Can be called /// multiple times, which will then add additional where conditions, prefixed /// with 'AND'. /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // By default, key = value /// qb.r#where("key", "value"); /// /// // Other operators can be used with a separating space /// qb.r#where("key >", 4); /// /// // If you don't like the r#where syntax, you can use the wher method instead /// qb.wher("key >", 4); /// ``` pub fn r#where(&mut self, key: &str, value: impl Any) -> &mut Self { self._where_string(key, value, "AND") } /// Alias method for `where`, as using the `where` method requires /// using the raw identifier `r#where`. pub fn wher(&mut self, key: &str, value: impl Any) -> &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: impl Any) -> &mut Self { self._where_string(key, value, "OR") } /// Specify a `where in` clause for the query pub fn where_in(&mut self, key: &str, values: Vec) -> &mut Self { self._where_in(key, values, "IN", "AND") } /// Specify a `where in` clause for the query, prefixed with `or` pub fn or_where_in(&mut self, key: &str, values: Vec) -> &mut Self { self._where_in(key, values, "IN", "OR") } /// Specify a `where not in` clause for the query pub fn where_not_in(&mut self, key: &str, values: Vec) -> &mut Self { self._where_in(key, values, "NOT IN", "AND") } /// Specify a `where not in` clause for the query, prefixed with `or` pub fn or_where_not_in(&mut self, key: &str, values: Vec) -> &mut Self { self._where_in(key, values, "NOT IN", "OR") } // -------------------------------------------------------------------------- // ! Other Query Modifier methods // -------------------------------------------------------------------------- /// Set a key and value for an insert or update query pub fn set(&mut self, key: &str, value: impl Any) -> &mut Self { let key = self.driver.quote_identifier(key); self.state .append_set_array_keys(&key) .append_values(Box::new(value)); self } /// Set a map of data for an insert or update query pub fn set_map(&mut self, data: HashMap) -> &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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Note that the value is not escaped, due to it often being a column /// qb.join("table1", "column1", "<>", "foo", JoinType::Inner); /// ``` 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.append_group_array(field); let group_string = String::from(" GROUP BY ") + &self.state.get_group_array().join(","); self.state.set_group_string(&group_string); self } /// Add an order by clause to the query pub fn order_by(&mut self, field: &str, direction: OrderDirection) -> &mut Self { let field = self.driver.quote_identifier(field); let dir = match direction { OrderDirection::Asc => String::from("ASC"), OrderDirection::Desc => String::from("DESC"), OrderDirection::Rand => self.driver.random(), }; self.state.append_order_map(&field, &dir); let mut order_clauses: Vec = vec![]; for (f, dir) in self.state.get_order_map() { let clause = String::clone(f) + " " + &dir; &order_clauses.push(clause); } let order_str = "\nORDER BY ".to_string() + &order_clauses.join(", "); self.state.set_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 { self.state .append_query_map(QueryClauseType::GroupStart, " ", "("); self } /// Start a logical grouping, prefixed with `not` pub fn not_group_start(&mut self) -> &mut Self { self.state .append_query_map(QueryClauseType::GroupStart, " AND ", "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(&mut self) { let sql = self.get_compiled_select(); self.run(&sql) } /// 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) { let sql = self.get_compiled_insert(table); self.run(&sql) } /// Execute the generated update query pub fn update(&mut self, table: &str) { let sql = self.get_compiled_update(table); self.run(&sql) } /// Execute the generated delete query pub fn delete(&mut self, table: &str) { let sql = self.get_compiled_delete(table); self.run(&sql) } // -------------------------------------------------------------------------- // ! SQL Returning Methods // -------------------------------------------------------------------------- /// 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, "") } /// Get the generated SQL for an insert query pub fn get_compiled_insert(&self, table: &str) -> String { self.compile(QueryType::Insert, table) } /// Get the generated SQL for an update query pub fn get_compiled_update(&self, table: &str) -> String { self.compile(QueryType::Update, table) } /// Get the generated SQL for a delete query pub fn get_compiled_delete(&self, table: &str) -> String { self.compile(QueryType::Delete, table) } // -------------------------------------------------------------------------- // ! Miscellaneous Methods // -------------------------------------------------------------------------- /// Get a new instance of the query builder pub fn reset(&mut self) -> &Self { self.state = QueryState::new(); self } /// Execute an SQL query pub fn query(&mut self, sql: &str) { unimplemented!(); } // -------------------------------------------------------------------------- // ! Implementation Details // -------------------------------------------------------------------------- fn _like( &mut self, field: &str, value: impl Any, position: LikeWildcard, like: &str, conj: &str, ) -> &mut Self { let field = self.driver.quote_identifier(field); let like = format!("{} {} ?", field, like); let value: Box = Box::new(value); let string_val = value.downcast_ref::().unwrap(); let value = match position { LikeWildcard::Before => format!("%{}", string_val), LikeWildcard::After => format!("{}%s", string_val), LikeWildcard::Both => format!("%{}%", string_val), }; self.state .append_query_map(QueryClauseType::Like, conj, &like); self.state.append_where_values(Box::new(value)); self } fn _having(&mut self, key: &str, values: Vec, conj: &str) -> &mut Self { let keys = self._where(key, values); for k in keys { self._having_key(&k, conj); } self } fn _having_key(&mut self, key: &str, conj: &str) -> &mut Self { let field = key.trim().split(" ").collect::>(); let mut item = self.driver.quote_identifier(field[0]);; let item2 = if field.len() == 1 { String::from("=?") } else { format!(" {} ?", &field[1]) }; item += &item2; self.state.append_having_map(conj, &item); self } fn _where(&mut self, key: &str, values: Vec) -> Vec { for x in values { self.state.append_where_values(Box::new(x)); } vec![String::from(key)] } fn _where_in( &mut self, key: &str, values: Vec, in_str: &str, conj: &str, ) -> &mut Self { let key = self.driver.quote_identifier(key); let placeholders = vec!["?"; values.len()]; for value in values { self.state.append_where_values(Box::new(value)); } let str = format!("{} {} ({}) ", key, in_str, placeholders.join(",")); self.state .append_query_map(QueryClauseType::WhereIn, conj, &str); self } fn _where_string(&mut self, key: &str, value: impl Any, conj: &str) -> &mut Self { let keys = self._where(key, vec![value]); for k in keys { self._where_string_key(&k, conj); } self } fn _where_string_key(&mut self, key: &str, conj: &str) { let field = key.trim().split(" ").collect::>(); let last_item = self.state.get_query_map_last(); let mut item = self.driver.quote_identifier(field[0]); let item2 = if field.len() == 1 { String::from("=?") } else { format!(" {} ?", &field[1]) }; item += &item2; let conj = if last_item.is_some() { let last_item = last_item.unwrap(); match last_item.clause_type { QueryClauseType::GroupStart => String::from(""), _ => format!(" {} ", conj), } } else { format!(" {} ", conj) }; self.state .append_query_map(QueryClauseType::Where, &conj, &item); } 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 mut sql = parts.join(""); // Add limit/ offset sql = self.driver.limit(&sql, self.state.limit, self.state.offset); // Handle explain if self.state.explain == true { self.driver.explain(&sql) } else { sql } } 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), } } fn run(&mut self, _sql: &str) { let mut values: Vec> = vec![]; values.append(self.state.get_values()); values.append(self.state.get_where_values()); // @TODO determine query result type // @TODO prepare/execute query, and return result unimplemented!(); } } #[derive(Debug)] struct QueryClause { clause_type: QueryClauseType, conjunction: String, string: String, } impl QueryClause { fn new(clause_type: QueryClauseType, conjunction: &str, string: &str) -> Self { QueryClause { clause_type, conjunction: conjunction.to_string(), string: string.to_string(), } } fn to_string(&self) -> String { format!("{}{}", self.conjunction, self.string) } } #[derive(Debug)] struct QueryState { pub explain: bool, select_string: String, from_string: String, set_string: String, order_string: String, group_string: String, // Keys for insert/update statement set_array_keys: Vec, // Order by clause order_map: HashMap, // Group by clause group_array: Vec, // Values to apply to prepared statements values: Vec>, // Values to apply to where clauses in prepared statements where_values: Vec>, pub limit: Option, pub offset: Option, // Query components for complex selects query_map: Vec, // Query components for having clauses having_map: Vec, } impl Default for QueryState { fn default() -> Self { QueryState { explain: false, 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_map: HashMap::new(), group_array: vec![], values: vec![], where_values: vec![], limit: None, offset: None, query_map: vec![], having_map: vec![], } } } impl QueryState { fn new() -> Self { QueryState::default() } fn append_select_string(&mut self, s: &str) -> &mut Self { self.select_string += s; self } fn prepend_select_string(&mut self, s: &str) -> &mut Self { self.select_string = String::from(s) + &self.select_string; self } fn append_group_array(&mut self, field: &str) -> &mut Self { self.group_array.push(String::from(field)); self } fn append_having_map(&mut self, conj: &str, s: &str) -> &mut Self { let conj = if self.having_map.len() == 0 { String::from(" HAVING ") } else { format!(" {} ", conj) }; self.having_map .push(QueryClause::new(QueryClauseType::Having, &conj, s)); self } fn append_order_map(&mut self, key: &str, dir: &str) -> &mut Self { self.order_map.insert(String::from(key), String::from(dir)); self } fn append_set_array_keys(&mut self, key: &str) -> &mut Self { self.set_array_keys.push(key.to_string()); self } fn append_values(&mut self, val: Box) -> &mut Self { self.values.push(val); self } fn append_where_values(&mut self, val: Box) -> &mut Self { self.where_values.push(val); self } fn append_query_map(&mut self, clause_type: QueryClauseType, conj: &str, s: &str) -> &mut Self { let conj = if self.query_map.len() == 0 { " WHERE " } else { conj }; self.query_map.push(QueryClause::new(clause_type, conj, s)); self } fn get_from_string(&self) -> &str { &self.from_string } fn get_group_array(&self) -> &Vec { &self.group_array } fn get_group_string(&self) -> &str { &self.group_string } fn get_having_map(&self) -> &Vec { &self.having_map } fn get_query_map(&self) -> &Vec { &self.query_map } fn get_query_map_last(&self) -> Option<&QueryClause> { if self.query_map.len() == 0 { return None; } let index = self.query_map.len() - 1; Some(&self.query_map[index]) } fn get_select_string(&self) -> &str { &self.select_string } fn get_set_array_keys(&self) -> &Vec { &self.set_array_keys } fn get_set_string(&self) -> &str { &self.set_string } fn get_order_map(&self) -> &HashMap { &self.order_map } fn get_order_string(&self) -> &str { &self.order_string } fn get_values(&mut self) -> &mut Vec> { &mut self.values } fn get_where_values(&mut self) -> &mut Vec> { &mut self.where_values } fn set_from_string(&mut self, s: &str) -> &mut Self { self.from_string = String::from(s); self } fn set_group_string(&mut self, s: &str) -> &mut Self { self.group_string = String::from(s); self } fn set_order_string(&mut self, order_string: &str) -> &mut Self { self.order_string = String::from(order_string); self } } #[cfg(test)] mod tests { use super::*; #[test] fn set_key_value() { let mut qb = QueryBuilder::default(); qb.set("foo", "bar"); assert_eq!(qb.state.get_set_array_keys()[0], "\"foo\""); assert!(qb.state.get_values()[0].is::<&str>()); // @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" ); } #[test] fn set_hashmap() { let mut qb = QueryBuilder::default(); let mut authors = HashMap::new(); authors.insert(String::from("Chinua Achebe"), String::from("Nigeria")); authors.insert(String::from("Rabindranath Tagore"), String::from("India")); authors.insert(String::from("Anita Nair"), String::from("India")); qb.set_map(authors); // 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); } #[test] fn set_where_in() { let mut qb = QueryBuilder::default(); qb.from("test").where_in("foo", vec![0, 1, 2, 3, 4, 5]); let sql = qb.get_compiled_select(); let expected = "SELECT *\nFROM \"test\" WHERE \"foo\" IN (?,?,?,?,?,?) "; assert_eq!(sql, expected); } }