From 2e793284dd34ecc5b6bc19cf195927e25f91075a Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Thu, 11 Apr 2019 15:30:10 -0400 Subject: [PATCH] Put most of the features together for actually assembling the sql --- src/enums.rs | 61 +++++++ src/lib.rs | 1 + src/query_builder.rs | 293 ++++++++++--------------------- src/query_builder/query_state.rs | 224 +++++++++++++++++++++++ 4 files changed, 382 insertions(+), 197 deletions(-) create mode 100644 src/enums.rs create mode 100644 src/query_builder/query_state.rs diff --git a/src/enums.rs b/src/enums.rs new file mode 100644 index 0000000..966634e --- /dev/null +++ b/src/enums.rs @@ -0,0 +1,61 @@ +//! Common enums + +/// 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)] +pub enum QueryClauseType { + /// Ending a parenthetical grouping + GroupEnd, + /// Starting a parenthetical grouping + GroupStart, + /// A join clause + Join, + /// A like clause + Like, + /// A where clause + Where, + /// A where in clause + WhereIn, +} diff --git a/src/lib.rs b/src/lib.rs index 7f05abf..a17f30b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,6 +8,7 @@ #![allow(unused_variables)] pub mod drivers; +pub mod enums; pub mod query_builder; pub mod types; diff --git a/src/query_builder.rs b/src/query_builder.rs index dbf2fd6..907ae78 100644 --- a/src/query_builder.rs +++ b/src/query_builder.rs @@ -1,64 +1,16 @@ //! 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; -/// 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, -} - -#[derive(Debug)] -enum QueryClauseType { - GroupEnd, - GroupStart, - Join, - Like, - Where, - WhereIn, -} +use query_state::QueryState; #[derive(Debug)] enum QueryType { @@ -68,116 +20,6 @@ enum QueryType { Delete, } -#[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_where_values(&mut self, val: Wild) -> &mut Self { - self.where_values.push(val); - - 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 { @@ -232,20 +74,16 @@ impl QueryBuilder { /// Adds the `distinct` keyword to a query pub fn distinct(&mut self) -> &mut Self { - self.state.select_string = String::from(" DISTINCT") + &self.state.select_string; + self.state.prepend_select_string(" DISTINCT"); self } /// Specify the database table to select from pub fn from(&mut self, table_name: &str) -> &mut Self { - let ident_vec = String::from(table_name) - .split(" ") - .into_iter() - .map(|s| self.driver.quote_identifier(s)) - .collect::>(); + let from_str = split_map_join(table_name, " ", |s| self.driver.quote_identifier(s)); - self.state.from_string = ident_vec.join(" "); + self.state.set_from_string(&from_str); self } @@ -295,9 +133,9 @@ impl QueryBuilder { /// 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.where_values.push(value); + self.state.append_where_values(value); - self + unimplemented!(); } /// Specify a condition for a `where` clause where a column has a value @@ -337,8 +175,7 @@ impl QueryBuilder { /// 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 - self.state.set_array_keys.push(key.to_string()); - self.state.values.push(value); + self.state.append_set_array_keys(key).append_values(value); self } @@ -393,9 +230,11 @@ impl QueryBuilder { /// 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.append_group_array(field); - self.state.group_string = " GROUP BY ".to_string() + &self.state.group_array.join(","); + let group_string = String::from(" GROUP BY ") + &self.state.get_group_array().join(","); + + self.state.set_group_string(&group_string); self } @@ -413,11 +252,11 @@ impl QueryBuilder { OrderDirection::Desc => String::from("DESC"), OrderDirection::Rand => String::from("RAND"), }; - self.state.order_array.insert(field, dir); + self.state.append_order_map(&field, &dir); let mut order_clauses: Vec = vec![]; - for (f, dir) in &self.state.order_array { + for (f, dir) in self.state.get_order_map() { let clause = String::clone(f) + " " + &dir; &order_clauses.push(clause); } @@ -428,7 +267,7 @@ impl QueryBuilder { unimplemented!(); }; - self.state.order_string = order_str; + self.state.set_order_string(&order_str); self } @@ -453,7 +292,7 @@ impl QueryBuilder { /// Start a logical grouping in the current query pub fn group_start(&mut self) -> &mut Self { - let conj = if self.state.query_map.len() == 0 { + let conj = if self.state.query_map_empty() { " WHERE " } else { " " @@ -467,7 +306,7 @@ impl QueryBuilder { /// Start a logical grouping, prefixed with `not` pub fn not_group_start(&mut self) -> &mut Self { - let conj = if self.state.query_map.len() == 0 { + let conj = if self.state.query_map_empty() { " WHERE " } else { " AND " @@ -539,23 +378,29 @@ impl QueryBuilder { // -------------------------------------------------------------------------- /// Get the generated SQL for a select query - pub fn get_compiled_select(self) -> String { - unimplemented!(); + 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) -> String { - unimplemented!(); + 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) -> String { - unimplemented!(); + 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) -> String { - unimplemented!(); + pub fn get_compiled_delete(&self, table: &str) -> String { + self.compile(QueryType::Delete, table) } // -------------------------------------------------------------------------- @@ -563,7 +408,7 @@ impl QueryBuilder { // -------------------------------------------------------------------------- /// Get a new instance of the query builder - pub fn reset_query(mut self) -> Self { + pub fn reset(&mut self) -> &Self { self.state = QueryState::new(); self @@ -594,7 +439,7 @@ impl QueryBuilder { LikeWildcard::Both => format!("%{}%", *string_val), }; - let conj = if self.state.query_map.len() == 0 { + let conj = if self.state.query_map_empty() { " WHERE " } else { conj @@ -624,11 +469,62 @@ impl QueryBuilder { } fn compile(&self, query_type: QueryType, table: &str) -> String { - unimplemented!(); + // 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 { - unimplemented!(); + 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), + } } } @@ -642,11 +538,14 @@ mod tests { qb.set("foo", Box::new("bar")); - assert_eq!(qb.state.set_array_keys[0], "foo"); - assert!(qb.state.values[0].is::<&str>()); + 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.values[0].downcast_ref::<&str>().unwrap(), "bar"); + assert_eq!( + *qb.state.get_values()[0].downcast_ref::<&str>().unwrap(), + "bar" + ); } #[test] @@ -667,7 +566,7 @@ mod tests { 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); + assert_eq!(qb.state.get_set_array_keys().len(), 3); + assert_eq!(qb.state.get_values().len(), 3); } } diff --git a/src/query_builder/query_state.rs b/src/query_builder/query_state.rs new file mode 100644 index 0000000..0dd3691 --- /dev/null +++ b/src/query_builder/query_state.rs @@ -0,0 +1,224 @@ +use super::*; + +#[derive(Debug)] +pub 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(), + } + } + + pub fn to_string(&self) -> String { + format!("{}{}", self.conjunction, self.string) + } +} + +#[derive(Debug)] +pub 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_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 { + 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 { + pub fn new() -> Self { + QueryState::default() + } + + pub fn append_select_string(&mut self, s: &str) -> &mut Self { + self.select_string += s; + + self + } + + pub fn prepend_select_string(&mut self, s: &str) -> &mut Self { + self.select_string = String::from(s) + &self.select_string; + + self + } + + pub fn append_group_array(&mut self, field: &str) -> &mut Self { + self.group_array.push(String::from(field)); + + self + } + + pub fn append_having_map( + &mut self, + clause_type: QueryClauseType, + conj: &str, + s: &str, + ) -> &mut Self { + self.having_map.push(QueryClause::new(clause_type, conj, s)); + + self + } + + pub fn append_order_map(&mut self, key: &str, dir: &str) -> &mut Self { + self.order_map.insert(String::from(key), String::from(dir)); + + self + } + + pub fn append_set_array_keys(&mut self, key: &str) -> &mut Self { + self.set_array_keys.push(key.to_string()); + + self + } + + pub fn append_values(&mut self, val: Wild) -> &mut Self { + self.values.push(val); + + self + } + + pub fn append_where_values(&mut self, val: Wild) -> &mut Self { + self.where_values.push(val); + + 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 get_from_string(&self) -> &str { + &self.from_string + } + + pub fn get_group_array(&self) -> &Vec { + &self.group_array + } + + pub fn get_group_string(&self) -> &str { + &self.group_string + } + + pub fn get_having_map(&self) -> &Vec { + &self.having_map + } + + pub fn get_query_map(&self) -> &Vec { + &self.query_map + } + + pub fn get_select_string(&self) -> &str { + &self.select_string + } + + pub fn get_set_array_keys(&self) -> &Vec { + &self.set_array_keys + } + + pub fn get_set_string(&self) -> &str { + &self.set_string + } + + pub fn get_order_map(&self) -> &HashMap { + &self.order_map + } + + pub fn get_order_string(&self) -> &str { + &self.order_string + } + + pub fn get_values(&self) -> &Vec { + &self.values + } + + pub fn get_where_values(&self) -> &Vec { + &self.where_values + } + + pub fn having_map_empty(&self) -> bool { + self.having_map.len() == 0 + } + + pub fn set_from_string(&mut self, s: &str) -> &mut Self { + self.from_string = String::from(s); + + self + } + + pub fn set_group_string(&mut self, s: &str) -> &mut Self { + self.group_string = String::from(s); + + self + } + + pub fn set_order_string(&mut self, order_string: &str) -> &mut Self { + self.order_string = String::from(order_string); + + self + } + + pub fn query_map_empty(&self) -> bool { + self.query_map.len() == 0 + } +}