From 0e1c6755b09f235765d9fd6b5eee4ca7fd1384ca Mon Sep 17 00:00:00 2001 From: Timothy Warren Date: Tue, 9 Apr 2019 14:13:37 -0400 Subject: [PATCH] Implement more --- src/drivers.rs | 45 +++++----- src/drivers/mssql.rs | 14 +-- src/drivers/mysql.rs | 14 +-- src/drivers/postgres.rs | 8 +- src/drivers/sqlite.rs | 12 ++- src/main.rs | 3 +- src/query_builder.rs | 188 ++++++++++++++++++++++++++++++++++------ 7 files changed, 211 insertions(+), 73 deletions(-) diff --git a/src/drivers.rs b/src/drivers.rs index d92be86..e148c9c 100644 --- a/src/drivers.rs +++ b/src/drivers.rs @@ -4,16 +4,16 @@ use std::fmt; #[cfg(feature = "postgres")] -mod postgres; +pub mod postgres; #[cfg(feature = "sqlite")] -mod sqlite; +pub mod sqlite; #[cfg(feature = "mysql")] -mod mysql; +pub mod mysql; #[cfg(feature = "mssql")] -mod mssql; +pub mod mssql; #[derive(Debug)] struct Connection; @@ -22,12 +22,20 @@ struct Connection; #[derive(Debug)] struct QueryResult; -struct DriverBase { - escape_char_open: char, - escape_char_close: char, - has_truncate: bool, +/// Empty Driver implementation +/// +/// Good for general testing +#[derive(Debug)] +pub struct DefaultDriver; + +impl DefaultDriver { + pub fn new() -> Self { + DefaultDriver {} + } } +impl DatabaseDriver for DefaultDriver {} + /// Database Driver Trait /// /// Interface between the database connection library and the query builder @@ -39,7 +47,7 @@ pub trait DatabaseDriver: fmt::Debug { } /// Vector version of `quote_identifier` - fn quote_identifiers<'a>(&self, identifiers: Vec<&'a str>) -> Vec { + fn quote_identifiers(&self, identifiers: Vec<&str>) -> Vec { let mut output: Vec = vec![]; for identifier in identifiers { @@ -81,7 +89,7 @@ pub trait DatabaseDriver: fmt::Debug { if tier.starts_with(open_char) && tier.ends_with(close_char) { trimmed_tiers.push(tier.to_string()); } else { - let mut tier = format!("{}{}{}", open_char, tier, close_char); + let tier = format!("{}{}{}", open_char, tier, close_char); trimmed_tiers.push(tier.to_string()); } } @@ -90,26 +98,17 @@ pub trait DatabaseDriver: fmt::Debug { // @TODO Fix functional calls in 'select' queries } - /// Runs a basic sql query on the database - fn query(&self, query: &str) -> Result<(), ()>; + // Runs a basic sql query on the database + // fn query(&self, query: &str) -> Result<(), ()>; } #[cfg(test)] mod tests { use super::*; - #[derive(Debug)] - struct TestDriver; - - impl DatabaseDriver for TestDriver { - fn query(&self, _query: &str) -> Result<(), ()> { - Ok(()) - } - } - #[test] fn test_quote_identifier() { - let driver = TestDriver {}; + let driver = DefaultDriver::new(); assert_eq!( driver.quote_identifier("foo, bar, baz"), @@ -123,7 +122,7 @@ mod tests { #[test] fn test_quote_identifiers() { - let driver = TestDriver {}; + let driver = DefaultDriver::new(); assert_eq!( driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]), diff --git a/src/drivers/mssql.rs b/src/drivers/mssql.rs index 280d2d4..c0c908e 100644 --- a/src/drivers/mssql.rs +++ b/src/drivers/mssql.rs @@ -3,16 +3,18 @@ use super::*; #[derive(Debug)] pub struct MSSQL; +impl MSSQL { + pub fn new() -> Self { + MSSQL {} + } +} + impl DatabaseDriver for MSSQL { /// Get which characters are used to delimit identifiers /// such as tables, and columns fn _quotes(&self) -> (char, char) { ('[', ']') } - - fn query(&self, _query: &str) -> Result<(), ()> { - Ok(()) - } } #[cfg(test)] @@ -21,7 +23,7 @@ mod tests { #[test] fn test_quote_identifier_bracket_quote() { - let driver = MSSQL {}; + let driver = MSSQL::new(); assert_eq!( driver.quote_identifier("foo, bar, baz"), @@ -35,7 +37,7 @@ mod tests { #[test] fn test_quote_identifiers_bracket_quote() { - let driver = MSSQL {}; + let driver = MSSQL::new(); assert_eq!( driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]), diff --git a/src/drivers/mysql.rs b/src/drivers/mysql.rs index d6ed429..8434c79 100644 --- a/src/drivers/mysql.rs +++ b/src/drivers/mysql.rs @@ -3,16 +3,18 @@ use super::*; #[derive(Debug)] pub struct MySQL; +impl MySQL { + pub fn new() -> Self { + MySQL {} + } +} + impl DatabaseDriver for MySQL { /// Get which characters are used to delimit identifiers /// such as tables, and columns fn _quotes(&self) -> (char, char) { ('`', '`') } - - fn query(&self, _query: &str) -> Result<(), ()> { - Ok(()) - } } #[cfg(test)] @@ -21,7 +23,7 @@ mod tests { #[test] fn test_quote_identifier_backtick_quote() { - let driver = MySQL {}; + let driver = MySQL::new(); assert_eq!( driver.quote_identifier("foo, bar, baz"), @@ -35,7 +37,7 @@ mod tests { #[test] fn test_quote_identifiers_backtick_quote() { - let driver = MySQL {}; + let driver = MySQL::new(); assert_eq!( driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]), diff --git a/src/drivers/postgres.rs b/src/drivers/postgres.rs index 6eeedc6..8a76256 100644 --- a/src/drivers/postgres.rs +++ b/src/drivers/postgres.rs @@ -3,8 +3,10 @@ use super::*; #[derive(Debug)] pub struct Postgres; -impl DatabaseDriver for Postgres { - fn query(&self, _query: &str) -> Result<(), ()> { - Ok(()) +impl Postgres { + pub fn new() -> Self { + Postgres {} } } + +impl DatabaseDriver for Postgres { } diff --git a/src/drivers/sqlite.rs b/src/drivers/sqlite.rs index 27de3a9..55ac9c9 100644 --- a/src/drivers/sqlite.rs +++ b/src/drivers/sqlite.rs @@ -3,12 +3,10 @@ use super::*; #[derive(Debug)] pub struct SQLite; -impl DatabaseDriver for SQLite { - fn quote_identifier(&self, identifier: &str) -> String { - String::from(identifier) - } - - fn query(&self, _query: &str) -> Result<(), ()> { - Ok(()) +impl SQLite { + pub fn new() -> Self { + SQLite {} } } + +impl DatabaseDriver for SQLite { } diff --git a/src/main.rs b/src/main.rs index d6116ef..cc45370 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,9 +1,10 @@ //! This main file is just for temparary testing use stringqb::query_builder::QueryBuilder; use stringqb::types::{SQLType, Type}; +use stringqb::drivers::postgres::Postgres; fn main() { - let mut qb = QueryBuilder::new(); + let mut qb = QueryBuilder::new(Postgres::new()); qb.set("foo", Box::new("bar")) .set("bar", Box::new(12)) diff --git a/src/query_builder.rs b/src/query_builder.rs index c5d19a7..8a96d62 100644 --- a/src/query_builder.rs +++ b/src/query_builder.rs @@ -4,7 +4,7 @@ use std::any::Any; use std::collections::HashMap; -use crate::drivers::DatabaseDriver; +use crate::drivers::{ DatabaseDriver, DefaultDriver }; /// The position of the wildcard(s) /// for a `like` clause @@ -35,21 +35,22 @@ pub enum JoinType { } /// The sort direction -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum OrderDirection { /// Sort Ascending Asc, /// Sort Descending Desc, + /// Random Sort + Rand, } #[derive(Debug)] enum QueryClauseType { - AndGroupStart, GroupEnd, GroupStart, + Join, Like, - OrGroupStart, Where, WhereIn, } @@ -62,11 +63,11 @@ struct QueryClause { } impl QueryClause { - pub fn new(clause_type: QueryClauseType, conjunction: String, string: String) -> Self { + pub fn new(clause_type: QueryClauseType, conjunction: &str, string: &str) -> Self { QueryClause { clause_type, - conjunction, - string, + conjunction: conjunction.to_string(), + string: string.to_string(), } } } @@ -86,7 +87,7 @@ struct QueryState { order_array: HashMap, // Group by clause - group_array: HashMap, + group_array: Vec, // Values to apply to prepared statements values: Vec>, @@ -116,7 +117,7 @@ impl Default for QueryState { set_array_keys: vec![], order_array: HashMap::new(), - group_array: HashMap::new(), + group_array: vec![], values: vec![], where_values: vec![], @@ -133,21 +134,43 @@ 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 set_from_string(&mut self, s:&str) -> &mut Self { + self.from_string = s.to_owned(); + + self + } } /// The struct representing a query builder -#[derive(Default, Debug)] +#[derive(Debug)] pub struct QueryBuilder { state: QueryState, - driver: Option>, + 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 { - /// Creates a new QueryBuilder instance - pub fn new() -> Self { + /// Create a new QueryBuilder instance with a driver + pub fn new(driver: impl DatabaseDriver + 'static) -> Self { QueryBuilder { state: QueryState::new(), - driver: None, + driver: Box::new(driver), } } @@ -296,19 +319,82 @@ impl QueryBuilder { 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, condition: &str, join_type: JoinType) -> &mut Self { - unimplemented!(); + 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.query_map.push(QueryClause::new( + QueryClauseType::Join, + &conjunction, + &condition + )); + + self } /// Add a group by clause to the query pub fn group_by(&mut self, field: &str) -> &mut Self { - unimplemented!(); + 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 { - unimplemented!(); + 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 @@ -331,27 +417,73 @@ impl QueryBuilder { /// Start a logical grouping in the current query pub fn group_start(&mut self) -> &mut Self { - unimplemented!(); + if self.state.query_map.len() == 0 { + self.state.query_map.push(QueryClause::new( + QueryClauseType::GroupStart, + " ", + "(" + )); + } else { + self.state.query_map.push(QueryClause::new( + QueryClauseType::GroupStart, + " WHERE ", + "(" + )); + } + + self } /// Start a logical grouping, prefixed with `not` pub fn not_group_start(&mut self) -> &mut Self { - unimplemented!(); + if self.state.query_map.len() == 0 { + self.state.query_map.push(QueryClause::new( + QueryClauseType::GroupStart, + " WHERE ", + "(" + )); + } else { + self.state.query_map.push(QueryClause::new( + QueryClauseType::GroupStart, + " AND ", + "(" + )); + } + + self } /// Start a logical grouping, prefixed with `or` pub fn or_group_start(&mut self) -> &mut Self { - unimplemented!(); + self.state.query_map.push(QueryClause::new( + QueryClauseType::GroupStart, + "", + " OR (" + )); + + self } /// Start a logical grouping, prefixed with `or not` pub fn or_not_group_start(&mut self) -> &mut Self { - unimplemented!(); + self.state.query_map.push(QueryClause::new( + QueryClauseType::GroupStart, + "", + " OR NOT (" + )); + + self } /// End the current logical grouping pub fn group_end(&mut self) -> &mut Self { - unimplemented!(); + self.state.query_map.push(QueryClause::new( + QueryClauseType::GroupEnd, + "", + ")" + )); + + self } // -------------------------------------------------------------------------- @@ -414,8 +546,10 @@ impl QueryBuilder { // -------------------------------------------------------------------------- /// Get a new instance of the query builder - pub fn reset_query(&mut self) -> Self { - QueryBuilder::new() + pub fn reset_query(mut self) -> Self { + self.state = QueryState::new(); + + self } } @@ -425,7 +559,7 @@ mod tests { #[test] fn set_key_value() { - let mut qb = QueryBuilder::new(); + let mut qb = QueryBuilder::default(); qb.set("foo", Box::new("bar")); @@ -438,7 +572,7 @@ mod tests { #[test] fn set_hashmap() { - let mut qb = QueryBuilder::new(); + let mut qb = QueryBuilder::default(); let mut authors: HashMap> = HashMap::new(); authors.insert(