//! Query Builder //! //! The QueryBuilder creates sql queries from chained methods use std::any::Any; use std::collections::HashMap; use crate::drivers::{DatabaseDriver, DefaultDriver}; /// 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 { /// An `INNER` join Inner, /// An `OUTER` join Outer, /// A `LEFT` join Left, /// A `RIGHT` join Right, /// A `LEFT OUTER` join LeftOuter, /// A `RIGHT OUTER` join RightOuter, } /// The sort direction #[derive(Debug, PartialEq)] pub enum OrderDirection { /// Sort Ascending Asc, /// Sort Descending Desc, /// Random Sort Rand, } #[derive(Debug)] enum QueryClauseType { GroupEnd, GroupStart, Join, Like, Where, WhereIn, } #[derive(Debug)] struct QueryClause { clause_type: QueryClauseType, conjunction: String, string: String, } impl QueryClause { pub fn new(clause_type: QueryClauseType, conjunction: &str, string: &str) -> Self { QueryClause { clause_type, conjunction: conjunction.to_string(), string: string.to_string(), } } } #[derive(Debug)] struct QueryState { select_string: String, from_string: String, set_string: String, order_string: String, group_string: String, // Keys for insert/update statement set_array_keys: Vec, // Order by clause order_array: 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>, limit: Option, 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 { select_string: String::from(""), from_string: String::from(""), set_string: String::from(""), order_string: String::from(""), group_string: String::from(""), set_array_keys: vec![], order_array: HashMap::new(), group_array: vec![], values: vec![], where_values: vec![], limit: None, offset: None, query_map: vec![], having_map: vec![], } } } impl QueryState { pub fn new() -> Self { QueryState::default() } pub fn append_select_string(&mut self, s: &str) -> &mut Self { self.select_string += s; self } pub fn append_query_map( &mut self, clause_type: QueryClauseType, conj: &str, s: &str, ) -> &mut Self { self.query_map.push(QueryClause::new(clause_type, conj, s)); self } pub fn set_from_string(&mut self, s: &str) -> &mut Self { self.from_string = s.to_owned(); self } } /// The struct representing a query builder #[derive(Debug)] pub struct QueryBuilder { state: QueryState, driver: Box, } impl Default for QueryBuilder { /// Creates a new QueryBuilder instance with default driver fn default() -> Self { QueryBuilder { state: QueryState::new(), driver: Box::new(DefaultDriver::new()), } } } impl QueryBuilder { /// Create a new QueryBuilder instance with a driver pub fn new(driver: impl DatabaseDriver + 'static) -> Self { QueryBuilder { state: QueryState::new(), driver: Box::new(driver), } } // -------------------------------------------------------------------------- // ! Select Queries // -------------------------------------------------------------------------- /// Set the fields to select from the database as a string pub fn select(&mut self, fields: &str) -> &mut Self { unimplemented!(); } /// Set the fields to select from the database as a Vector pub fn select_vec(&mut self, fields: Vec<&str>) -> &mut Self { let fields = fields.join(","); self.select(&fields) } /// Adds the `distinct` keyword to a query pub fn distinct(&mut self) -> &mut Self { unimplemented!(); } /// Specify the database table to select from pub fn from(&mut self, table_name: &str) -> &mut Self { // @TODO properly escape the table name self.state.from_string = table_name.to_string(); self } // -------------------------------------------------------------------------- // ! 'Like' methods // -------------------------------------------------------------------------- /// Creates a `like` clause in the sql statement pub fn like(&mut self, field: &str, value: Box, position: LikeWildcard) -> &mut Self { unimplemented!(); } /// Generates an OR Like clause pub fn or_like( &mut self, field: &str, value: Box, position: LikeWildcard, ) -> &mut Self { unimplemented!(); } /// Generates a NOI Like clause pub fn not_like( &mut self, field: &str, value: Box, position: LikeWildcard, ) -> &mut Self { unimplemented!(); } /// Generates an OR NOT Like clause pub fn or_not_like( &mut self, field: &str, value: Box, position: LikeWildcard, ) -> &mut Self { unimplemented!(); } // -------------------------------------------------------------------------- // ! Having methods // -------------------------------------------------------------------------- /// Add a `having` clause to the query pub fn having(&mut self, key: &str, value: Box) -> &mut Self { unimplemented!(); } /// Add a `having` clause to the query, prefixed with an `or` pub fn or_having(&mut self, key: &str, value: Box) -> &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: Box) -> &mut Self { // @TODO actually implement setting the keys for the where self.state.where_values.push(value); self } // Specify a condition for a `where` clause where a column has a value pub fn where_eq(&mut self, key: &str, value: Box) -> &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: Box) -> &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: Box) -> &mut Self { // @TODO figure a way to make this easier to use self.state.set_array_keys.push(key.to_string()); self.state.values.push(value); self } /// Set a map of data for an insert or update query pub fn set_map(&mut self, data: HashMap>) -> &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::Left => "LEFT ", JoinType::Inner => "INNER ", JoinType::LeftOuter => "LEFT OUTER ", JoinType::Outer => "OUTER ", JoinType::Right => "RIGHT ", JoinType::RightOuter => "RIGHT OUTER", }; let conjunction = "\n".to_string() + join_type + "JOIN "; self.state .append_query_map(QueryClauseType::Join, &conjunction, &condition); self } /// Add a group by clause to the query pub fn group_by(&mut self, field: &str) -> &mut Self { self.state.group_array.push(field.to_string()); self.state.group_string = " GROUP BY ".to_string() + &self.state.group_array.join(","); self } /// Add an order by clause to the query pub fn order_by(&mut self, field: &str, direction: OrderDirection) -> &mut Self { if direction == OrderDirection::Rand { // @TODO handle random sorting unimplemented!(); } let field = self.driver.quote_identifier(field); let dir = match direction { OrderDirection::Asc => String::from("ASC"), OrderDirection::Desc => String::from("DESC"), OrderDirection::Rand => String::from("RAND"), }; self.state.order_array.insert(field, dir); let mut order_clauses: Vec = vec![]; for (f, dir) in &self.state.order_array { let clause = String::clone(f) + " " + &dir; &order_clauses.push(clause); } let order_str = if direction != OrderDirection::Rand { "\nORDER BY ".to_string() + &order_clauses.join(", ") } else { unimplemented!(); }; self.state.order_string = order_str; self } /// Add a limit to the query pub fn limit(&mut self, limit: usize) -> &mut Self { self.state.limit = Some(limit); self } /// Add an offset to the query pub fn offset(&mut self, offset: usize) -> &mut Self { self.state.offset = Some(offset); self } // -------------------------------------------------------------------------- // ! Query Grouping Methods // -------------------------------------------------------------------------- /// Start a logical grouping in the current query pub fn group_start(&mut self) -> &mut Self { if self.state.query_map.len() == 0 { self.state .append_query_map(QueryClauseType::GroupStart, " ", "("); } else { self.state .append_query_map(QueryClauseType::GroupStart, " WHERE ", "("); } self } /// Start a logical grouping, prefixed with `not` pub fn not_group_start(&mut self) -> &mut Self { if self.state.query_map.len() == 0 { self.state .append_query_map(QueryClauseType::GroupStart, " WHERE NOT ", "("); } else { 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(self) -> Box { unimplemented!(); } /// Count all the rows in the specified database table pub fn count_all(self, table: &str) -> usize { unimplemented!(); } /// Execute the generated insert query pub fn insert(&mut self, table: &str) { // @TODO determine query result type unimplemented!(); } /// Execute the generated update query pub fn update(&mut self, table: &str) { // @TODO determine query result type unimplemented!(); } /// Execute the generated delete query pub fn delete(&mut self, table: &str) { unimplemented!(); } // -------------------------------------------------------------------------- // ! SQL Returning Methods // -------------------------------------------------------------------------- /// Get the generated SQL for a select query pub fn get_compiled_select(self) -> String { unimplemented!(); } /// Get the generated SQL for an insert query pub fn get_compiled_insert(self) -> String { unimplemented!(); } /// Get the generated SQL for an update query pub fn get_compiled_update(self) -> String { unimplemented!(); } /// Get the generated SQL for a delete query pub fn get_compiled_delete(self) -> String { unimplemented!(); } // -------------------------------------------------------------------------- // ! Miscellaneous Methods // -------------------------------------------------------------------------- /// Get a new instance of the query builder pub fn reset_query(mut self) -> Self { self.state = QueryState::new(); self } // -------------------------------------------------------------------------- // ! Implementation Details // -------------------------------------------------------------------------- fn compile() -> String { unimplemented!(); } } #[cfg(test)] mod tests { use super::*; #[test] fn set_key_value() { let mut qb = QueryBuilder::default(); qb.set("foo", Box::new("bar")); assert_eq!(qb.state.set_array_keys[0], "foo"); assert!(qb.state.values[0].is::<&str>()); // @TODO find a way to make this kind of operation much more ergonomic assert_eq!(*qb.state.values[0].downcast_ref::<&str>().unwrap(), "bar"); } #[test] fn set_hashmap() { let mut qb = QueryBuilder::default(); let mut authors: HashMap> = HashMap::new(); authors.insert( String::from("Chinua Achebe"), Box::new(String::from("Nigeria")), ); authors.insert( String::from("Rabindranath Tagore"), Box::new(String::from("India")), ); authors.insert(String::from("Anita Nair"), Box::new(String::from("India"))); qb.set_map(authors); // assert_eq!(qb.state.set_array_keys[0], "Chinua Achebe"); assert_eq!(qb.state.set_array_keys.len(), 3); assert_eq!(qb.state.values.len(), 3); } }