//! Query Builder //! //! The QueryBuilder creates sql queries from chained methods mod query_state; use std::collections::HashMap; use crate::drivers::{DatabaseDriver, DefaultDriver}; use crate::enums::*; use crate::split_map_join; use crate::types::Wild; use regex::Regex; use query_state::QueryState; #[derive(Debug)] enum QueryType { Select, Insert, Update, Delete, } /// The struct representing a query builder #[derive(Debug)] pub struct QueryBuilder { state: QueryState, driver: Box, } 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 /// /// ```ignore /// let mut qb = QueryBuilder::new(Driver::new()); /// /// // 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 /// /// ``` /// # 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""#); /// ``` 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 } /// Specify the database table to select from /// /// ```ignore /// let mut qb = QueryBuilder::new(Driver::new()); /// /// // 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 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.append_where_values(value); unimplemented!(); } /// 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) -> &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) -> &mut Self { unimplemented!(); } /// Specify a `where not in` clause for the query pub fn where_not_in(&mut self, key: &str, value: Vec) -> &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) -> &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 let key = self.driver.quote_identifier(key); self.state.append_set_array_keys(&key).append_values(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 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 { 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); 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 = if direction != OrderDirection::Rand { "\nORDER BY ".to_string() + &order_clauses.join(", ") } else { unimplemented!(); }; 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 { let conj = if self.state.query_map_empty() { " 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_empty() { " 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 { // 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 } // -------------------------------------------------------------------------- // ! 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::().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_empty() { " 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) -> HashMap { unimplemented!(); } fn _where_in(&mut self, key: &str, values: Vec) -> &mut Self { unimplemented!(); } fn _where_in_string(&mut self, key: &str, values: Vec) -> &mut Self { unimplemented!(); } fn _where_string(&mut self, key: &str, value: Wild) -> &mut Self { unimplemented!(); } 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 } 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), } } } #[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.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 = 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.get_set_array_keys().len(), 3); assert_eq!(qb.state.get_values().len(), 3); } }