Misc bugfixes, doc updates

This commit is contained in:
Timothy Warren 2019-07-19 12:45:18 -04:00
parent aad53f0f20
commit eb838306ec
6 changed files with 233 additions and 88 deletions

View File

@ -102,7 +102,9 @@ pub trait DatabaseDriver {
}
// Runs a basic sql query on the database
// fn query(&self, sql: &str) -> Result<impl Any, impl Error>;
fn query(&self, sql: &str) -> Result<Box<dyn Any>, Box<dyn Any>> {
Ok(Box::new(String::from(sql)))
}
/// Prepares an sql statement for the database
fn prepare(&self, sql: &str) -> Result<(), ()> {

View File

@ -33,18 +33,6 @@ impl PostgresDriver {
self.connection = RefCell::new(Some(connection));
}
pub fn query(&self, sql: &str) -> Result<Vec<Row>, Error> {
if self.connection.borrow().is_none() {
panic!("No database connection.");
}
self.connection
.borrow_mut()
.as_mut()
.unwrap()
.query(sql, &[])
}
}
impl DatabaseDriver for PostgresDriver {
@ -55,4 +43,22 @@ impl DatabaseDriver for PostgresDriver {
fn random(&self) -> String {
String::from(" RANDOM()")
}
fn query(&self, sql: &str) -> Result<Box<dyn Any>, Box<dyn Any>> {
if self.connection.borrow().is_none() {
panic!("No database connection.");
}
let result = self
.connection
.borrow_mut()
.as_mut()
.unwrap()
.query(sql, &[]);
match result {
Ok(res) => Ok(Box::new(res)),
Err(e) => Err(Box::new(e)),
}
}
}

View File

@ -35,12 +35,7 @@ pub mod prelude {
//! This includes enum types, traits,
//! the Query Builder, and individual database drivers.
pub use crate::drivers::DatabaseDriver;
pub use crate::query_builder::{
JoinType,
LikeWildcard,
OrderDirection,
QueryBuilder,
};
pub use crate::query_builder::{JoinType, LikeWildcard, OrderDirection, QueryBuilder};
#[cfg(feature = "postgres")]
/// Postgres Driver

View File

@ -79,7 +79,36 @@ enum QueryType {
Delete,
}
/// The struct representing a query builder
/// QueryBuilder for general SQL queries
///
/// ```
/// 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());
///
/// // Each builder method returns a mutable reference to itself so
/// // the methods are chainable
/// qb.select("field as f")
/// .from("table t")
/// .inner_join("table_two tt", "field2 as ff", "=", "f")
/// .wher("f >", 3);
///
/// // Since they are references, you do not have to chain.
/// let sql = qb.get_compiled_select();
///
/// # assert_eq!(
/// # sql,
/// # r#"SELECT "field" AS "f"
/// FROM "table" "t"
/// INNER JOIN "table_two" "tt" ON "field2" AS "ff"=f
/// WHERE "f" > ?"#
/// # );
/// ```
pub struct QueryBuilder {
/// The struct to store the query builder info
state: QueryState,
@ -87,9 +116,7 @@ pub struct QueryBuilder {
}
/// The struct representing a prepared statement
pub struct Prepared {
}
pub struct Prepared {}
impl Default for QueryBuilder {
/// Creates a new QueryBuilder instance with default driver
@ -109,19 +136,19 @@ impl QueryBuilder {
/// ```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;
/// // Postgres Driver (If enabled)
/// let pgDriver = PostgresDriver::new("postgres://");
///
/// // The query builder must be mutable to be useful
/// let mut qb = QueryBuilder::new(DefaultDriver::new());
/// #[cfg(feature = "sqlite")]
/// // SQLite Driver (memory)
/// let liteMemoryDriver = SQLiteDriver::new(":memory:");
///
/// // Each builder method returns a mutable reference to itself so
/// // the methods are chainable
/// qb.select("field f").from("table");
/// #[cfg(feature = "sqlite")]
/// // SQLite Driver (file)
/// let liteDriver = SQLiteDriver::new("/path/to/db.sqlite3");
///
/// // Since they are references, you do not have to chain.
/// let sql = qb.get_compiled_select();
/// // The variable to the query builder must be mutable
/// let mut queryBuilder = QueryBuilder::new(pgDriver);
/// ```
pub fn new(driver: impl DatabaseDriver + 'static) -> Self {
QueryBuilder {
@ -137,28 +164,13 @@ impl QueryBuilder {
/// Set the fields to select from the database as a string
///
/// ```no_run
/// # let mut qb = stringqb::query_builder::QueryBuilder::default();
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // You can also alias field names
/// qb.select("foo as bar");
/// ```
pub fn select(&mut self, fields: &str) -> &mut Self {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?i) as ").unwrap();
};
let fields = split_map_join(fields, ",", |s| {
if !RE.is_match(s) {
return self.driver.quote_identifier(s.trim());
}
// Do a operation similar to split_map_join for the
// regex matches, quoting each identifier
RE.split(s)
.into_iter()
.map(|p| self.driver.quote_identifier(p))
.collect::<Vec<String>>()
.join(" as ")
});
let fields = self.quote_fields(fields);
self.state.append_select_string(&fields);
@ -168,7 +180,8 @@ impl QueryBuilder {
/// Set the fields to select from the database as a Vector
///
/// ```no_run
/// # let mut qb = stringqb::query_builder::QueryBuilder::default();
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // You can also alias via a vector of fields
/// qb.select_vec(vec!["foo as bar", "baz"]);
/// ```
@ -195,8 +208,9 @@ impl QueryBuilder {
/// Specify the database table to select from
///
/// ```no_run
/// # let mut qb = stringqb::query_builder::QueryBuilder::default();
/// // You can specifiy an alias for the table
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // You can specify an alias for the table
/// qb.from("products p");
/// ```
pub fn from(&mut self, table_name: &str) -> &mut Self {
@ -215,7 +229,7 @@ impl QueryBuilder {
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = stringqb::query_builder::QueryBuilder::default();
/// # let mut qb = QueryBuilder::default();
/// // Search for a value that ends with "foo"
/// qb.like("field", String::from("foo"), LikeWildcard::Before);
///
@ -233,7 +247,7 @@ impl QueryBuilder {
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = stringqb::query_builder::QueryBuilder::default();
/// # let mut qb = QueryBuilder::default();
/// // Search for a value that ends with "foo"
/// qb.or_like("field", String::from("foo"), LikeWildcard::Before);
///
@ -251,7 +265,7 @@ impl QueryBuilder {
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = stringqb::query_builder::QueryBuilder::default();
/// # let mut qb = QueryBuilder::default();
/// // Search for a value that does not end with "foo"
/// qb.not_like("field", String::from("foo"), LikeWildcard::Before);
///
@ -269,7 +283,7 @@ impl QueryBuilder {
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = stringqb::query_builder::QueryBuilder::default();
/// # let mut qb = QueryBuilder::default();
/// // Search for a value that does not end with "foo"
/// qb.or_not_like("field", String::from("foo"), LikeWildcard::Before);
///
@ -393,16 +407,37 @@ impl QueryBuilder {
}
/// Specify a `where in` clause for the query, prefixed with `or`
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // Look for a set of rows matching the values passed
/// qb.or_where_in("key", vec![1,2,3]);
/// ```
pub fn or_where_in(&mut self, key: &str, values: Vec<impl Any>) -> &mut Self {
self._where_in(key, values, "IN", "OR")
}
/// Specify a `where not in` clause for the query
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // Look for a set of rows not matching the values passed
/// qb.where_not_in("key", vec![1,2,3]);
/// ```
pub fn where_not_in(&mut self, key: &str, values: Vec<impl Any>) -> &mut Self {
self._where_in(key, values, "NOT IN", "AND")
}
/// Specify a `where not in` clause for the query, prefixed with `or`
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // Look for a set of rows not matching the values passed
/// qb.or_where_not_in("key", vec![1,2,3]);
/// ```
pub fn or_where_not_in(&mut self, key: &str, values: Vec<impl Any>) -> &mut Self {
self._where_in(key, values, "NOT IN", "OR")
}
@ -412,6 +447,15 @@ impl QueryBuilder {
// --------------------------------------------------------------------------
/// Set a key and value for an insert or update query
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // Can be called multiple times to set multiple values
/// qb.set("foo", 3)
/// .set("bar", 4)
/// .insert("table");
/// ```
pub fn set(&mut self, key: &str, value: impl Any) -> &mut Self {
let key = self.driver.quote_identifier(key);
self.state
@ -431,11 +475,25 @@ impl QueryBuilder {
}
/// Convenience method for a `left` join
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // Note that the value is not prepared/escaped, due to it often being a column
/// qb.left_join("table1", "column1", "<>", "foo");
/// ```
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
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // Note that the value is not prepared/escaped, due to it often being a column
/// qb.inner_join("table1", "column1", "<>", "foo");
/// ```
pub fn inner_join(&mut self, table: &str, col: &str, op: &str, value: &str) -> &mut Self {
self.join(table, col, op, value, JoinType::Inner)
}
@ -445,7 +503,7 @@ impl QueryBuilder {
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // Note that the value is not escaped, due to it often being a column
/// // Note that the value is not prepared/escaped, due to it often being a column
/// qb.join("table1", "column1", "<>", "foo", JoinType::Inner);
/// ```
pub fn join(
@ -456,9 +514,16 @@ impl QueryBuilder {
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 col = self.quote_fields(col);
let table = self.quote_table(table);
let condition = format!(
"{} ON {}{}{}",
table,
&col,
op,
value
);
let join_type = match join_type {
JoinType::Cross => "CROSS ",
@ -520,6 +585,11 @@ impl QueryBuilder {
/// Add an offset to the query
pub fn offset(&mut self, offset: usize) -> &mut Self {
assert!(
self.state.offset.is_some(),
"You must set a limit to set an offset"
);
self.state.offset = Some(offset);
self
@ -531,16 +601,28 @@ impl QueryBuilder {
/// Start a logical grouping in the current query
pub fn group_start(&mut self) -> &mut Self {
let conj = if ! self.state.has_where_clause() {
"\nWHERE "
} else {
" "
};
self.state
.append_query_map(QueryClauseType::GroupStart, " ", "(");
.append_query_map(QueryClauseType::GroupStart, conj, "(");
self
}
/// Start a logical grouping, prefixed with `not`
pub fn not_group_start(&mut self) -> &mut Self {
let conj = if ! self.state.has_where_clause() {
"\nWHERE "
} else {
" AND "
};
self.state
.append_query_map(QueryClauseType::GroupStart, " AND ", "NOT (");
.append_query_map(QueryClauseType::GroupStart, conj, "NOT (");
self
}
@ -580,7 +662,7 @@ impl QueryBuilder {
/// # let mut qb = QueryBuilder::default();
/// // The get() method actually calls the driver to run
/// // the SQL query
/// let query = db.select_vec(vec!["foo", "bar"])
/// let query = qb.select_vec(vec!["foo", "bar"])
/// .from("table t")
/// .get();
/// ```
@ -602,7 +684,7 @@ impl QueryBuilder {
/// # let mut qb = QueryBuilder::default();
/// // The insert() method actually calls the driver to run
/// // the SQL query
/// let query = db.set("foo", 3)
/// let query = qb.set("foo", 3)
/// .insert("table");
/// ```
pub fn insert(&mut self, table: &str) {
@ -618,7 +700,7 @@ impl QueryBuilder {
/// # let mut qb = QueryBuilder::default();
/// // The update() method actually calls the driver to run
/// // the SQL query
/// let query = db.set("foo", 3)
/// let query = qb.set("foo", 3)
/// .wher("foo", 4)
/// .update("table");
/// ```
@ -629,6 +711,15 @@ impl QueryBuilder {
}
/// Execute the generated delete query
///
/// ```no_run
/// # use stringqb::prelude::*;
/// # let mut qb = QueryBuilder::default();
/// // The delete() method actually calls the driver to run
/// // the SQL query
/// let query = qb.wher("foo", 3)
/// .delete("table");
/// ```
pub fn delete(&mut self, table: &str) {
let sql = self.get_compiled_delete(table);
@ -670,14 +761,14 @@ impl QueryBuilder {
// --------------------------------------------------------------------------
/// Get a new instance of the query builder
pub fn reset(&mut self) -> &Self {
pub fn reset(&mut self) -> &mut Self {
self.state = QueryState::new();
self
}
/// Execute an SQL query with no parameters
pub fn basic_query(&mut self, sql: &str) {
pub fn basic_query(&mut self, sql: &str) -> Result<Box<dyn Any>, Box<dyn Any>> {
self.driver.query(sql)
}
@ -691,6 +782,32 @@ impl QueryBuilder {
unimplemented!();
}
/// Quotes table column(s)/field(s) accounting for 'as' aliases
fn quote_fields(&mut self, fields: &str) -> String {
lazy_static! {
static ref RE: Regex = Regex::new(r"(?i) as ").unwrap();
};
split_map_join(fields, ",", |s| {
if !RE.is_match(s) {
return self.driver.quote_identifier(s.trim());
}
// Do a operation similar to split_map_join for the
// regex matches, quoting each identifier
RE.split(s)
.into_iter()
.map(|p| self.driver.quote_identifier(p))
.collect::<Vec<String>>()
.join(" AS ")
})
}
/// Quotes table(s), accounting for aliases
pub fn quote_table(&mut self, table: &str) -> String {
split_map_join(table, " ", |s| self.driver.quote_identifier(s))
}
// --------------------------------------------------------------------------
// ! Implementation Details
// --------------------------------------------------------------------------
@ -703,6 +820,12 @@ impl QueryBuilder {
like: &str,
conj: &str,
) -> &mut Self {
let conj = if ! self.state.has_where_clause() {
"\nWHERE "
} else {
conj
};
let field = self.driver.quote_identifier(field);
let like = format!("{} {} ?", field, like);
@ -775,6 +898,12 @@ impl QueryBuilder {
let str = format!("{} {} ({}) ", key, in_str, placeholders.join(","));
let conj = if ! self.state.has_where_clause() {
"\nWHERE "
} else {
conj
};
self.state
.append_query_map(QueryClauseType::WhereIn, conj, &str);
@ -805,6 +934,12 @@ impl QueryBuilder {
item += &item2;
let conj = if self.state.query_map.len() == 0 || ( ! self.state.has_where_clause()) {
String::from("\nWHERE")
} else {
String::from(conj)
};
let conj = if last_item.is_some() {
let last_item = last_item.unwrap();
@ -820,6 +955,8 @@ impl QueryBuilder {
.append_query_map(QueryClauseType::Where, &conj, &item);
}
fn compile(&self, query_type: QueryType, table: &str) -> String {
// Get the base clause for the query
let base_sql = self.compile_type(query_type, &self.driver.quote_identifier(table));
@ -893,7 +1030,7 @@ impl QueryBuilder {
// @TODO determine query result type
// @TODO prepare/execute query, and return result
let stmt = self.prepare(sql);
self.execute(&stmt, values)
self.execute(&stmt, &values)
}
}
@ -1041,12 +1178,6 @@ impl QueryState {
}
fn append_query_map(&mut self, clause_type: QueryClauseType, conj: &str, s: &str) -> &mut Self {
let conj = if self.query_map.len() == 0 {
" WHERE "
} else {
conj
};
self.query_map.push(QueryClause::new(clause_type, conj, s));
self
@ -1110,6 +1241,20 @@ impl QueryState {
&mut self.where_values
}
fn has_where_clause(&self) -> bool {
let has_clause = false;
for clause in self.query_map.iter() {
match clause.clause_type {
QueryClauseType::Where => return true,
QueryClauseType::WhereIn => return true,
_ => (),
}
}
has_clause
}
fn set_from_string(&mut self, s: &str) -> &mut Self {
self.from_string = String::from(s);
@ -1172,7 +1317,7 @@ mod tests {
qb.from("test").where_in("foo", vec![0, 1, 2, 3, 4, 5]);
let sql = qb.get_compiled_select();
let expected = "SELECT *\nFROM \"test\" WHERE \"foo\" IN (?,?,?,?,?,?) ";
let expected = "SELECT *\nFROM \"test\"\nWHERE \"foo\" IN (?,?,?,?,?,?) ";
assert_eq!(sql, expected);
}

View File

@ -120,16 +120,13 @@ impl<'a, T: ?Sized> From<&'a T> for ToDriverOutput<'a>
//from_value!(f64);
//from_value!(Vec<u8>);
/// Types that can be converted to a type that the driver understands
pub trait ToDriver {
fn to_driver(&self) -> Result<ToDriverOutput<'_>, ()>;
}
/// A trait for types that can be created from the result of a query on the driver
pub trait FromDriver: Sized {
}
pub trait FromDriver: Sized {}
/// Enum struct for mapping between database and Rust types
#[derive(Clone, Copy, PartialEq, PartialOrd, Eq, Ord, Debug, Hash)]

View File

@ -15,7 +15,7 @@ fn select_keys_as_query() {
let sql = qb.select("foo as bar, baz").from("a").get_compiled_select();
assert_eq!(sql, "SELECT \"foo\" as \"bar\",\"baz\"\nFROM \"a\"");
assert_eq!(sql, "SELECT \"foo\" AS \"bar\",\"baz\"\nFROM \"a\"");
}
#[test]
@ -54,7 +54,7 @@ fn select_where() {
qb.from("test").r#where("foo", "bar");
let sql = qb.get_compiled_select();
let expected = "SELECT *\nFROM \"test\" WHERE \"foo\"=?";
let expected = "SELECT *\nFROM \"test\"\nWHERE \"foo\"=?";
assert_eq!(sql, expected);
}
@ -66,7 +66,7 @@ fn select_where_in() {
qb.from("test").where_in("foo", vec![0, 1, 2, 3, 4, 5]);
let sql = qb.get_compiled_select();
let expected = "SELECT *\nFROM \"test\" WHERE \"foo\" IN (?,?,?,?,?,?) ";
let expected = "SELECT *\nFROM \"test\"\nWHERE \"foo\" IN (?,?,?,?,?,?) ";
assert_eq!(sql, expected);
}