Browse Source

Misc bugfixes, doc updates

master
Timothy Warren 4 months ago
parent
commit
eb838306ec
6 changed files with 233 additions and 88 deletions
  1. +3
    -1
      src/drivers.rs
  2. +18
    -12
      src/drivers/postgres.rs
  3. +1
    -6
      src/lib.rs
  4. +205
    -60
      src/query_builder.rs
  5. +3
    -6
      src/types.rs
  6. +3
    -3
      tests/integration_test.rs

+ 3
- 1
src/drivers.rs 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<(), ()> {

+ 18
- 12
src/drivers/postgres.rs 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)),
}
}
}

+ 1
- 6
src/lib.rs 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

+ 205
- 60
src/query_builder.rs 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,21 +934,29 @@ 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();

match last_item.clause_type {
QueryClauseType::GroupStart => String::from(""),
_ => format!(" {} ", conj),
_ => format!("{} ", conj),
}
} else {
format!(" {} ", conj)
format!("{} ", conj)
};

self.state
.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);
}

+ 3
- 6
src/types.rs View File

@@ -86,8 +86,8 @@ pub enum ToDriverOutput<'a> {
// Generically allow any type that can be converted into a ValueRef
// to be converted into a ToSqlOutput as well.
impl<'a, T: ?Sized> From<&'a T> for ToDriverOutput<'a>
where
&'a T: Into<ValueRef<'a>>,
where
&'a T: Into<ValueRef<'a>>,
{
fn from(t: &'a T) -> Self {
ToDriverOutput::Borrowed(t.into())
@@ -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)]

+ 3
- 3
tests/integration_test.rs 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);
}

Loading…
Cancel
Save