Implement more

This commit is contained in:
Timothy Warren 2019-04-09 14:13:37 -04:00
parent f9979e9470
commit 0e1c6755b0
7 changed files with 211 additions and 73 deletions

View File

@ -4,16 +4,16 @@
use std::fmt; use std::fmt;
#[cfg(feature = "postgres")] #[cfg(feature = "postgres")]
mod postgres; pub mod postgres;
#[cfg(feature = "sqlite")] #[cfg(feature = "sqlite")]
mod sqlite; pub mod sqlite;
#[cfg(feature = "mysql")] #[cfg(feature = "mysql")]
mod mysql; pub mod mysql;
#[cfg(feature = "mssql")] #[cfg(feature = "mssql")]
mod mssql; pub mod mssql;
#[derive(Debug)] #[derive(Debug)]
struct Connection; struct Connection;
@ -22,12 +22,20 @@ struct Connection;
#[derive(Debug)] #[derive(Debug)]
struct QueryResult; struct QueryResult;
struct DriverBase { /// Empty Driver implementation
escape_char_open: char, ///
escape_char_close: char, /// Good for general testing
has_truncate: bool, #[derive(Debug)]
pub struct DefaultDriver;
impl DefaultDriver {
pub fn new() -> Self {
DefaultDriver {}
}
} }
impl DatabaseDriver for DefaultDriver {}
/// Database Driver Trait /// Database Driver Trait
/// ///
/// Interface between the database connection library and the query builder /// Interface between the database connection library and the query builder
@ -39,7 +47,7 @@ pub trait DatabaseDriver: fmt::Debug {
} }
/// Vector version of `quote_identifier` /// Vector version of `quote_identifier`
fn quote_identifiers<'a>(&self, identifiers: Vec<&'a str>) -> Vec<String> { fn quote_identifiers(&self, identifiers: Vec<&str>) -> Vec<String> {
let mut output: Vec<String> = vec![]; let mut output: Vec<String> = vec![];
for identifier in identifiers { for identifier in identifiers {
@ -81,7 +89,7 @@ pub trait DatabaseDriver: fmt::Debug {
if tier.starts_with(open_char) && tier.ends_with(close_char) { if tier.starts_with(open_char) && tier.ends_with(close_char) {
trimmed_tiers.push(tier.to_string()); trimmed_tiers.push(tier.to_string());
} else { } else {
let mut tier = format!("{}{}{}", open_char, tier, close_char); let tier = format!("{}{}{}", open_char, tier, close_char);
trimmed_tiers.push(tier.to_string()); trimmed_tiers.push(tier.to_string());
} }
} }
@ -90,26 +98,17 @@ pub trait DatabaseDriver: fmt::Debug {
// @TODO Fix functional calls in 'select' queries // @TODO Fix functional calls in 'select' queries
} }
/// Runs a basic sql query on the database // Runs a basic sql query on the database
fn query(&self, query: &str) -> Result<(), ()>; // fn query(&self, query: &str) -> Result<(), ()>;
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
#[derive(Debug)]
struct TestDriver;
impl DatabaseDriver for TestDriver {
fn query(&self, _query: &str) -> Result<(), ()> {
Ok(())
}
}
#[test] #[test]
fn test_quote_identifier() { fn test_quote_identifier() {
let driver = TestDriver {}; let driver = DefaultDriver::new();
assert_eq!( assert_eq!(
driver.quote_identifier("foo, bar, baz"), driver.quote_identifier("foo, bar, baz"),
@ -123,7 +122,7 @@ mod tests {
#[test] #[test]
fn test_quote_identifiers() { fn test_quote_identifiers() {
let driver = TestDriver {}; let driver = DefaultDriver::new();
assert_eq!( assert_eq!(
driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]), driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]),

View File

@ -3,16 +3,18 @@ use super::*;
#[derive(Debug)] #[derive(Debug)]
pub struct MSSQL; pub struct MSSQL;
impl MSSQL {
pub fn new() -> Self {
MSSQL {}
}
}
impl DatabaseDriver for MSSQL { impl DatabaseDriver for MSSQL {
/// Get which characters are used to delimit identifiers /// Get which characters are used to delimit identifiers
/// such as tables, and columns /// such as tables, and columns
fn _quotes(&self) -> (char, char) { fn _quotes(&self) -> (char, char) {
('[', ']') ('[', ']')
} }
fn query(&self, _query: &str) -> Result<(), ()> {
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
@ -21,7 +23,7 @@ mod tests {
#[test] #[test]
fn test_quote_identifier_bracket_quote() { fn test_quote_identifier_bracket_quote() {
let driver = MSSQL {}; let driver = MSSQL::new();
assert_eq!( assert_eq!(
driver.quote_identifier("foo, bar, baz"), driver.quote_identifier("foo, bar, baz"),
@ -35,7 +37,7 @@ mod tests {
#[test] #[test]
fn test_quote_identifiers_bracket_quote() { fn test_quote_identifiers_bracket_quote() {
let driver = MSSQL {}; let driver = MSSQL::new();
assert_eq!( assert_eq!(
driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]), driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]),

View File

@ -3,16 +3,18 @@ use super::*;
#[derive(Debug)] #[derive(Debug)]
pub struct MySQL; pub struct MySQL;
impl MySQL {
pub fn new() -> Self {
MySQL {}
}
}
impl DatabaseDriver for MySQL { impl DatabaseDriver for MySQL {
/// Get which characters are used to delimit identifiers /// Get which characters are used to delimit identifiers
/// such as tables, and columns /// such as tables, and columns
fn _quotes(&self) -> (char, char) { fn _quotes(&self) -> (char, char) {
('`', '`') ('`', '`')
} }
fn query(&self, _query: &str) -> Result<(), ()> {
Ok(())
}
} }
#[cfg(test)] #[cfg(test)]
@ -21,7 +23,7 @@ mod tests {
#[test] #[test]
fn test_quote_identifier_backtick_quote() { fn test_quote_identifier_backtick_quote() {
let driver = MySQL {}; let driver = MySQL::new();
assert_eq!( assert_eq!(
driver.quote_identifier("foo, bar, baz"), driver.quote_identifier("foo, bar, baz"),
@ -35,7 +37,7 @@ mod tests {
#[test] #[test]
fn test_quote_identifiers_backtick_quote() { fn test_quote_identifiers_backtick_quote() {
let driver = MySQL {}; let driver = MySQL::new();
assert_eq!( assert_eq!(
driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]), driver.quote_identifiers(vec!["\tfoo. bar", "baz", "fizz.\n\tbuzz.baz",]),

View File

@ -3,8 +3,10 @@ use super::*;
#[derive(Debug)] #[derive(Debug)]
pub struct Postgres; pub struct Postgres;
impl DatabaseDriver for Postgres { impl Postgres {
fn query(&self, _query: &str) -> Result<(), ()> { pub fn new() -> Self {
Ok(()) Postgres {}
} }
} }
impl DatabaseDriver for Postgres { }

View File

@ -3,12 +3,10 @@ use super::*;
#[derive(Debug)] #[derive(Debug)]
pub struct SQLite; pub struct SQLite;
impl DatabaseDriver for SQLite { impl SQLite {
fn quote_identifier(&self, identifier: &str) -> String { pub fn new() -> Self {
String::from(identifier) SQLite {}
}
fn query(&self, _query: &str) -> Result<(), ()> {
Ok(())
} }
} }
impl DatabaseDriver for SQLite { }

View File

@ -1,9 +1,10 @@
//! This main file is just for temparary testing //! This main file is just for temparary testing
use stringqb::query_builder::QueryBuilder; use stringqb::query_builder::QueryBuilder;
use stringqb::types::{SQLType, Type}; use stringqb::types::{SQLType, Type};
use stringqb::drivers::postgres::Postgres;
fn main() { fn main() {
let mut qb = QueryBuilder::new(); let mut qb = QueryBuilder::new(Postgres::new());
qb.set("foo", Box::new("bar")) qb.set("foo", Box::new("bar"))
.set("bar", Box::new(12)) .set("bar", Box::new(12))

View File

@ -4,7 +4,7 @@
use std::any::Any; use std::any::Any;
use std::collections::HashMap; use std::collections::HashMap;
use crate::drivers::DatabaseDriver; use crate::drivers::{ DatabaseDriver, DefaultDriver };
/// The position of the wildcard(s) /// The position of the wildcard(s)
/// for a `like` clause /// for a `like` clause
@ -35,21 +35,22 @@ pub enum JoinType {
} }
/// The sort direction /// The sort direction
#[derive(Debug)] #[derive(Debug, PartialEq)]
pub enum OrderDirection { pub enum OrderDirection {
/// Sort Ascending /// Sort Ascending
Asc, Asc,
/// Sort Descending /// Sort Descending
Desc, Desc,
/// Random Sort
Rand,
} }
#[derive(Debug)] #[derive(Debug)]
enum QueryClauseType { enum QueryClauseType {
AndGroupStart,
GroupEnd, GroupEnd,
GroupStart, GroupStart,
Join,
Like, Like,
OrGroupStart,
Where, Where,
WhereIn, WhereIn,
} }
@ -62,11 +63,11 @@ struct QueryClause {
} }
impl 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 { QueryClause {
clause_type, clause_type,
conjunction, conjunction: conjunction.to_string(),
string, string: string.to_string(),
} }
} }
} }
@ -86,7 +87,7 @@ struct QueryState {
order_array: HashMap<String, String>, order_array: HashMap<String, String>,
// Group by clause // Group by clause
group_array: HashMap<String, String>, group_array: Vec<String>,
// Values to apply to prepared statements // Values to apply to prepared statements
values: Vec<Box<dyn Any>>, values: Vec<Box<dyn Any>>,
@ -116,7 +117,7 @@ impl Default for QueryState {
set_array_keys: vec![], set_array_keys: vec![],
order_array: HashMap::new(), order_array: HashMap::new(),
group_array: HashMap::new(), group_array: vec![],
values: vec![], values: vec![],
where_values: vec![], where_values: vec![],
@ -133,21 +134,43 @@ impl QueryState {
pub fn new() -> Self { pub fn new() -> Self {
QueryState::default() 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 /// The struct representing a query builder
#[derive(Default, Debug)] #[derive(Debug)]
pub struct QueryBuilder { pub struct QueryBuilder {
state: QueryState, state: QueryState,
driver: Option<Box<dyn DatabaseDriver>>, driver: Box<dyn DatabaseDriver>,
}
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 { impl QueryBuilder {
/// Creates a new QueryBuilder instance /// Create a new QueryBuilder instance with a driver
pub fn new() -> Self { pub fn new(driver: impl DatabaseDriver + 'static) -> Self {
QueryBuilder { QueryBuilder {
state: QueryState::new(), state: QueryState::new(),
driver: None, driver: Box::new(driver),
} }
} }
@ -296,21 +319,84 @@ impl QueryBuilder {
self 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 /// Add a table join to the query
pub fn join(&mut self, table: &str, condition: &str, join_type: JoinType) -> &mut Self { pub fn join(&mut self, table: &str, col: &str, op: &str, value: &str, join_type: JoinType) -> &mut Self {
unimplemented!(); 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 /// Add a group by clause to the query
pub fn group_by(&mut self, field: &str) -> &mut Self { 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 /// Add an order by clause to the query
pub fn order_by(&mut self, field: &str, direction: OrderDirection) -> &mut Self { pub fn order_by(&mut self, field: &str, direction: OrderDirection) -> &mut Self {
if direction == OrderDirection::Rand {
// @TODO handle random sorting
unimplemented!(); 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<String> = 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 /// Add a limit to the query
pub fn limit(&mut self, limit: usize) -> &mut Self { pub fn limit(&mut self, limit: usize) -> &mut Self {
self.state.limit = Some(limit); self.state.limit = Some(limit);
@ -331,27 +417,73 @@ impl QueryBuilder {
/// Start a logical grouping in the current query /// Start a logical grouping in the current query
pub fn group_start(&mut self) -> &mut Self { 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` /// Start a logical grouping, prefixed with `not`
pub fn not_group_start(&mut self) -> &mut Self { 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` /// Start a logical grouping, prefixed with `or`
pub fn or_group_start(&mut self) -> &mut Self { 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` /// Start a logical grouping, prefixed with `or not`
pub fn or_not_group_start(&mut self) -> &mut Self { 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 /// End the current logical grouping
pub fn group_end(&mut self) -> &mut Self { 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 /// Get a new instance of the query builder
pub fn reset_query(&mut self) -> Self { pub fn reset_query(mut self) -> Self {
QueryBuilder::new() self.state = QueryState::new();
self
} }
} }
@ -425,7 +559,7 @@ mod tests {
#[test] #[test]
fn set_key_value() { fn set_key_value() {
let mut qb = QueryBuilder::new(); let mut qb = QueryBuilder::default();
qb.set("foo", Box::new("bar")); qb.set("foo", Box::new("bar"));
@ -438,7 +572,7 @@ mod tests {
#[test] #[test]
fn set_hashmap() { fn set_hashmap() {
let mut qb = QueryBuilder::new(); let mut qb = QueryBuilder::default();
let mut authors: HashMap<String, Box<dyn Any>> = HashMap::new(); let mut authors: HashMap<String, Box<dyn Any>> = HashMap::new();
authors.insert( authors.insert(