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;
#[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<String> {
fn quote_identifiers(&self, identifiers: Vec<&str>) -> Vec<String> {
let mut output: Vec<String> = 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",]),

View File

@ -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",]),

View File

@ -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",]),

View File

@ -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 { }

View File

@ -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 { }

View File

@ -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))

View File

@ -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<String, String>,
// Group by clause
group_array: HashMap<String, String>,
group_array: Vec<String>,
// Values to apply to prepared statements
values: Vec<Box<dyn Any>>,
@ -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<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 {
/// 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<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
@ -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<String, Box<dyn Any>> = HashMap::new();
authors.insert(