//! 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, } /// QueryBuilder for general SQL queries /// /// ``` /// 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 as f") /// .from("table t") /// .inner_join("table_two tt", "field2 as ff", "=", "f") /// .wher("f >", 3); /// /// // Since they are references, you do not have to chain. /// let sql = qb.get_compiled_select(); /// /// # assert_eq!( /// # sql, /// # r#"SELECT "field" AS "f" /// # FROM "table" "t" /// # INNER JOIN "table_two" "tt" ON "field2" AS "ff"=f /// # WHERE "f" > ?"# /// # ); /// ``` pub struct QueryBuilder { /// The struct to store the query builder info state: QueryState, driver: Box, } /// The struct representing a prepared statement pub struct Prepared {} 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::*; /// /// // Postgres Driver (If enabled) /// let pgDriver = PostgresDriver::new("postgres://user:pass@host:port/database"); /// /// // MySQL/MariaDB Driver, requires "mysql" feature /// let myDriver = MySQLDriver::new("mysql://user:pass@host:port/database"); /// /// // SQLite Driver (memory), requires "sqlite" feature /// #[cfg(feature = "sqlite")] /// let liteMemoryDriver = SQLiteDriver::new(":memory:"); /// /// // SQLite Driver (file), requires "sqlite" feature /// #[cfg(feature = "sqlite")] /// let liteDriver = SQLiteDriver::new("/path/to/db.sqlite3"); /// /// // The variable to the query builder must be mutable /// let mut queryBuilder = QueryBuilder::new(pgDriver); /// ``` 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 /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // You can also alias field names /// qb.select("foo as bar"); /// ``` pub fn select(&mut self, fields: &str) -> &mut Self { let fields = self.quote_fields(fields); self.state.append_select_string(&fields); self } /// Set the fields to select from the database as a Vector /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = 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 plan 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 /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // You can specify 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 = 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Search for a value that ends with "foo" /// qb.or_like("field", String::from("foo"), LikeWildcard::Before); /// /// // Search for a value that starts with "foo" /// qb.or_like("field", String::from("foo"), LikeWildcard::After); /// /// // Search for a value that has "foo" in it /// qb.or_like("field", String::from("foo"), LikeWildcard::Both); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Search for a value that does not end with "foo" /// qb.not_like("field", String::from("foo"), LikeWildcard::Before); /// /// // Search for a value that does not start with "foo" /// qb.not_like("field", String::from("foo"), LikeWildcard::After); /// /// // Search for a value that does not have "foo" in it /// qb.not_like("field", String::from("foo"), LikeWildcard::Both); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Search for a value that does not end with "foo" /// qb.or_not_like("field", String::from("foo"), LikeWildcard::Before); /// /// // Search for a value that does not start with "foo" /// qb.or_not_like("field", String::from("foo"), LikeWildcard::After); /// /// // Search for a value that does not have "foo" in it /// qb.or_not_like("field", String::from("foo"), LikeWildcard::Both); /// ``` 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` /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // By default, key = value /// qb.or_having("key", vec!["value"]); /// /// // Other operators can be used with a separating space /// qb.or_having("clues <", vec![88]); /// ``` 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`. /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // By default, key = value /// qb.wher("key", "value"); /// /// // Other operators can be used with a separating space /// qb.wher("key >", 4); /// ``` pub fn wher(&mut self, key: &str, value: impl Any) -> &mut Self { self._where_string(key, value, "AND") } /// Specify a condition for the `where` clause of the query, prefixed with `or` /// /// By default does not have any query grouping. /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // By default, key = value /// qb.or_where("key", "value"); /// /// // Other operators can be used with a separating space /// qb.or_where("key !=", "foo"); /// ``` 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. If called multiple times, /// will prefix the clause with `AND` /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Look for a set of rows matching the values passed /// qb.where_in("key", vec![1,2,3]); /// ``` 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` /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Look for a set of rows matching the values passed /// qb.or_where_in("key", vec![1,2,3]); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Look for a set of rows not matching the values passed /// qb.where_not_in("key", vec![1,2,3]); /// ``` 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` /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Look for a set of rows not matching the values passed /// qb.or_where_not_in("key", vec![1,2,3]); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Can be called multiple times to set multiple values /// qb.set("foo", 3) /// .set("bar", 4) /// .insert("table"); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// use std::collections::HashMap; /// /// 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) /// .insert("authors"); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Note that the value is not prepared/escaped, due to it often being a column /// qb.left_join("table1", "column1", "<>", "foo"); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // Note that the value is not prepared/escaped, due to it often being a column /// qb.inner_join("table1", "column1", "<>", "foo"); /// ``` 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 prepared/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 col = self.quote_fields(col); let table = self.quote_table(table); let condition = format!( "{} ON {}{}{}", table, &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 { assert!( self.state.offset.is_some(), "You must set a limit to set an offset" ); 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.has_where_clause() { "\nWHERE " } 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.has_where_clause() { "\nWHERE " } 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 generated select query /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // The get() method actually calls the driver to run /// // the SQL query /// let query = qb.select_vec(vec!["foo", "bar"]) /// .from("table t") /// .get(); /// ``` 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 /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // The insert() method actually calls the driver to run /// // the SQL query /// let query = qb.set("foo", 3) /// .insert("table"); /// ``` pub fn insert(&mut self, table: &str) { let sql = self.get_compiled_insert(table); self.run(&sql) } /// Execute the generated update query /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // The update() method actually calls the driver to run /// // the SQL query /// let query = qb.set("foo", 3) /// .wher("foo", 4) /// .update("table"); /// ``` pub fn update(&mut self, table: &str) { let sql = self.get_compiled_update(table); self.run(&sql) } /// Execute the generated delete query /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// // The delete() method actually calls the driver to run /// // the SQL query /// let query = qb.wher("foo", 3) /// .delete("table"); /// ``` pub fn delete(&mut self, table: &str) { let sql = self.get_compiled_delete(table); self.run(&sql) } /// Empty the table of all values /// /// ```no_run /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// let query = qb.truncate("table"); /// ``` pub fn truncate(&mut self, table: &str) -> Result, Box> { let sql = self.driver.truncate(table); self.basic_query(&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 { // The fields and values to insert must be specified assert!( self.state.get_set_array_keys().len() > 0, "You must use the `set` or `set_map` method to set columns and values to insert" ); self.compile(QueryType::Insert, table) } /// Get the generated SQL for an update query pub fn get_compiled_update(&self, table: &str) -> String { // Updates require fields and values assert!( self.state.get_set_array_keys().len() > 0, "You must use the `set` or `set_map` method to set columns and values to update" ); self.compile(QueryType::Update, table) } /// Get the generated SQL for a delete query pub fn get_compiled_delete(&self, table: &str) -> String { // Where clause required assert!( self.state.has_where_clause(), "You must specify a where clause for delete. To empty a table, use the `truncate` method." ); self.compile(QueryType::Delete, table) } // -------------------------------------------------------------------------- // ! Miscellaneous Methods // -------------------------------------------------------------------------- /// Get a new instance of the query builder pub fn reset(&mut self) -> &mut Self { self.state = QueryState::new(); self } /// Execute an SQL query with no parameters pub fn basic_query(&mut self, sql: &str) -> Result, Box> { self.driver.query(sql) } /// Prepare an SQL query pub fn prepare(&mut self, sql: &str) -> Prepared { unimplemented!(); } /// Execute a prepared statement pub fn execute(&mut self, stmt: &Prepared, params: &[Box]) { unimplemented!(); } /// Quotes table column(s)/field(s) accounting for 'as' aliases /// /// ``` /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// let fields = "a.b,c as cplus,d.a as x"; /// let quoted_fields = qb.quote_fields(fields); /// let expected = r#""a"."b","c" AS "cplus","d"."a" AS "x""#; /// /// assert_eq!(quoted_fields, expected); /// ``` pub fn quote_fields(&mut self, fields: &str) -> String { lazy_static! { static ref RE: Regex = Regex::new(r"(?i) as ").unwrap(); }; 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 ") }) } /// Quotes table(s), accounting for aliases /// /// ``` /// # use stringqb::prelude::*; /// # let mut qb = QueryBuilder::default(); /// let tables = "a,b.c,d dprime"; /// let quoted_tables = qb.quote_table(tables); /// let expected = r#""a","b"."c","d" "dprime""#; /// /// assert_eq!(quoted_tables, expected); /// ``` pub fn quote_table(&mut self, table: &str) -> String { split_map_join(table, " ", |s| self.driver.quote_identifier(s)) } // -------------------------------------------------------------------------- // ! Implementation Details // -------------------------------------------------------------------------- fn _like( &mut self, field: &str, value: impl Any, position: LikeWildcard, like: &str, conj: &str, ) -> &mut Self { let conj = if ! self.state.has_where_clause() { "\nWHERE " } else { conj }; 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(",")); let conj = if ! self.state.has_where_clause() { "\nWHERE " } else { conj }; 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 self.state.query_map.len() == 0 || ( ! self.state.has_where_clause()) { String::from("\nWHERE") } else { String::from(conj) }; 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 let stmt = self.prepare(sql); self.execute(&stmt, &values) } } #[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 { 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 has_where_clause(&self) -> bool { let has_clause = false; for clause in self.query_map.iter() { match clause.clause_type { QueryClauseType::Where => return true, QueryClauseType::WhereIn => return true, _ => (), } } has_clause } 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\"\nWHERE \"foo\" IN (?,?,?,?,?,?) "; assert_eq!(sql, expected); } }