diff --git a/src/drivers.rs b/src/drivers.rs index 1ca3563..4a29678 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -1,7 +1,8 @@ //! Database Drivers //! //! Drivers represent a connection to a specific type of database engine -use crate::split_map_join; +use crate::fns::split_map_join; +use regex::Regex; use std::fmt; #[cfg(feature = "postgres")] diff --git a/src/fns.rs b/src/fns.rs new file mode 100644 index 0000000..c2aa058 --- /dev/null +++ b/src/fns.rs @@ -0,0 +1,27 @@ +//! Utility / Helper functions that don't really belong anywhere else + +/// Split a string, apply a closure to each substring, +/// then join the string back together +/// +/// For example: +/// ``` +/// use stringqb::fns::split_map_join; +/// +/// let result = split_map_join("a\n,b, c\t,d", ",", |s| s.trim().to_string()); +/// assert_eq!("a,b,c,d", result); +/// ``` +pub fn split_map_join<'a>( + string: &'a str, + split_join_by: &str, + map_fn: impl (FnMut(&'a str) -> String), +) -> String { + string + .split(split_join_by) + .into_iter() + .map(map_fn) + .collect::>() + .join(split_join_by) +} + +#[cfg(test)] +mod tests {} \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index adff51d..0f69a8f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -12,44 +12,12 @@ extern crate lazy_static; pub mod drivers; pub mod enums; +pub mod fns; pub mod query_builder; -pub mod types; -/// Split a string, apply a closure to each substring, -/// then join the string back together -/// -/// For example: -/// ``` -/// use stringqb::split_map_join; -/// -/// let result = split_map_join("a\n,b, c\t,d", ",", |s| s.trim().to_string()); -/// assert_eq!("a,b,c,d", result); -/// ``` -pub fn split_map_join<'a>( - string: &'a str, - split_join_by: &str, - map_fn: impl (FnMut(&'a str) -> String), -) -> String { - string - .split(split_join_by) - .into_iter() - .map(map_fn) - .collect::>() - .join(split_join_by) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_split_map_join() { - let start = "a\t,b ,c\n,d"; - let expected = "a,b,c,d"; - - assert_eq!( - split_map_join(start, ",", |s| s.trim().to_string()), - expected - ); - } +pub mod prelude { + //! Re-exports important traits and types. + pub use crate::enums::*; + pub use crate::drivers::{DatabaseDriver, DefaultDriver}; + pub use crate::query_builder::QueryBuilder; } diff --git a/src/query_builder.rs b/src/query_builder.rs index 4b50610..39b9ebe 100644 --- a/src/query_builder.rs +++ b/src/query_builder.rs @@ -3,12 +3,12 @@ //! The QueryBuilder creates sql queries from chained methods mod query_state; +use std::any::Any; use std::collections::HashMap; use crate::drivers::{DatabaseDriver, DefaultDriver}; use crate::enums::*; -use crate::split_map_join; -use crate::types::Wild; +use crate::fns::split_map_join; use regex::Regex; use query_state::QueryState; @@ -31,6 +31,8 @@ pub struct QueryBuilder { 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(), @@ -41,6 +43,17 @@ impl Default for QueryBuilder { 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()); + /// ``` pub fn new(driver: impl DatabaseDriver + 'static) -> Self { QueryBuilder { state: QueryState::new(), @@ -125,22 +138,35 @@ impl QueryBuilder { // -------------------------------------------------------------------------- /// Creates a `like` clause in the sql statement - pub fn like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self { + /// + /// ```no_run + /// # use stringqb::prelude::*; + /// # let mut qb = stringqb::query_builder::QueryBuilder::default(); + /// // Search for a value that ends with "foo" + /// qb.like("field", Box::new("foo"), LikeWildcard::Before); + /// + /// // Search for a value that starts with "foo" + /// qb.like("field", Box::new("foo"), LikeWildcard::After); + /// + /// // Search for a value that has "foo" in it + /// qb.like("field", Box::new("foo"), LikeWildcard::Both); + /// ``` + pub fn like(&mut self, field: &str, value: Box, 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") + pub fn or_like(&mut self, field: &str, value: Box, position: LikeWildcard) -> &mut Self { + self._like(field, Box::new(value), position, "LIKE", "OR") } /// Generates a NOI Like clause - pub fn not_like(&mut self, field: &str, value: Wild, position: LikeWildcard) -> &mut Self { + pub fn not_like(&mut self, field: &str, value: Box, 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 { + pub fn or_not_like(&mut self, field: &str, value: Box, position: LikeWildcard) -> &mut Self { self._like(field, value, position, "NOT LIKE", "OR") } @@ -149,12 +175,12 @@ impl QueryBuilder { // -------------------------------------------------------------------------- /// Add a `having` clause to the query - pub fn having(&mut self, key: &str, value: Wild) -> &mut Self { + 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: Wild) -> &mut Self { + pub fn or_having(&mut self, key: &str, value: Box) -> &mut Self { unimplemented!(); } @@ -163,7 +189,7 @@ 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 { + 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.append_where_values(value); @@ -171,33 +197,33 @@ impl QueryBuilder { } /// 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 { + 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: Wild) -> &mut Self { + 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!(); + 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, value: Vec) -> &mut Self { - unimplemented!(); + 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, value: Vec) -> &mut Self { - unimplemented!(); + 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, value: Vec) -> &mut Self { - unimplemented!(); + pub fn or_where_not_in(&mut self, key: &str, values: Vec>) -> &mut Self { + self._where_in(key, values, "NOT IN", "OR") } // -------------------------------------------------------------------------- @@ -205,7 +231,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 { + pub fn set(&mut self, key: &str, value: Box) -> &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); @@ -214,7 +240,7 @@ impl QueryBuilder { } /// Set a map of data for an insert or update query - pub fn set_map(&mut self, data: HashMap) -> &mut Self { + pub fn set_map(&mut self, data: HashMap>) -> &mut Self { for (key, value) in data { self.set(&key, value); } @@ -454,7 +480,7 @@ impl QueryBuilder { fn _like( &mut self, field: &str, - value: Wild, + value: Box, position: LikeWildcard, like: &str, conj: &str, @@ -485,19 +511,34 @@ impl QueryBuilder { self } - fn _where(key: &str, values: Vec) -> HashMap { + fn _where(&mut self, key: &str, values: Vec>) -> HashMap> { + let mut map: HashMap> = HashMap::new(); + unimplemented!(); } - fn _where_in(&mut self, key: &str, values: Vec) -> &mut Self { - unimplemented!(); + 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(value); + } + + let conj = if self.state.query_map_empty() { + " WHERE " + } else { + conj + }; + + let str = format!("{} {} ({}) ", key, in_str, placeholders.join(",")); + + self.state.append_query_map(QueryClauseType::WhereIn, conj, &str); + + self } - fn _where_in_string(&mut self, key: &str, values: Vec) -> &mut Self { - unimplemented!(); - } - - fn _where_string(&mut self, key: &str, value: Wild) -> &mut Self { + fn _where_string(&mut self, key: &str, value: Box) -> &mut Self { unimplemented!(); } @@ -585,7 +626,7 @@ mod tests { fn set_hashmap() { let mut qb = QueryBuilder::default(); - let mut authors: HashMap = HashMap::new(); + let mut authors: HashMap> = HashMap::new(); authors.insert( String::from("Chinua Achebe"), Box::new(String::from("Nigeria")), @@ -602,4 +643,24 @@ mod tests { 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![ + Box::new(0), + Box::new(1), + Box::new(2), + Box::new(3), + Box::new(4), + Box::new(5) + ]); + + let sql = qb.get_compiled_select(); + let expected = "SELECT *\nFROM \"test\" WHERE \"foo\" IN (?,?,?,?,?,?) "; + + assert_eq!(sql, expected); + } } diff --git a/src/query_builder/query_state.rs b/src/query_builder/query_state.rs index 0dd3691..464b975 100644 --- a/src/query_builder/query_state.rs +++ b/src/query_builder/query_state.rs @@ -39,10 +39,10 @@ pub struct QueryState { group_array: Vec, // Values to apply to prepared statements - values: Vec, + values: Vec>, // Values to apply to where clauses in prepared statements - where_values: Vec, + where_values: Vec>, pub limit: Option, @@ -125,13 +125,13 @@ impl QueryState { self } - pub fn append_values(&mut self, val: Wild) -> &mut Self { + pub fn append_values(&mut self, val: Box) -> &mut Self { self.values.push(val); self } - pub fn append_where_values(&mut self, val: Wild) -> &mut Self { + pub fn append_where_values(&mut self, val: Box) -> &mut Self { self.where_values.push(val); self @@ -188,11 +188,11 @@ impl QueryState { &self.order_string } - pub fn get_values(&self) -> &Vec { + pub fn get_values(&self) -> &Vec> { &self.values } - pub fn get_where_values(&self) -> &Vec { + pub fn get_where_values(&self) -> &Vec> { &self.where_values } diff --git a/src/types.rs b/src/types.rs index 837aafd..00e2b36 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,11 +1,8 @@ //! Shared Types for different Database drivers use std::any::Any; -/// The Wild type is any type, until examined -pub type Wild = Box; - #[derive(Debug)] -struct Type(pub Wild); +struct Type(pub Box); /// Enum struct for mapping between database and Rust types #[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]