Skip to content

Commit

Permalink
Expand the query DSL to allow filter on more than just tables
Browse files Browse the repository at this point in the history
The main thing that stood out to me was that the final test,
`filter_then_select` didn't immedaitely pass once I made it compile. It
was ignoring the where clause of the parent source. This re-affirms to
me that I need to start looking for a more generic way to implement
this, instead of having all of these different concrete types.

In the short term, I've removed the default impl to force myself to
consider the implications of this method on the rest of the types. For
Join sources, I think it still makes sense to always return `None`,
since they only work with tables at the moment.

It's worth noting this now allows chaining `select` and `filter`, where
subsequent calls will override the previous. I think that's fine for
`select`, but `filter` should of course chain with `and`.

This has a lot of the same caveats that we've seen in the past when
moving things off of `Table` and onto `QuerySource`. The tests get a tad
bit verbose here, but I'm not sure if that's indicative of a problem.

Once again I need to do the selectable expression workaround. This can
go away once rust-lang/rfcs#1268 lands. I was
unable to add a blanket impl in these cases, since it overlapped with
the blanket impls defined for types like `expression::Eq`. This was
actually quite surprising to me, since both impls had concrete types
which could be known for a fact not to overlap.
  • Loading branch information
sgrif committed Oct 7, 2015
1 parent ed9a990 commit 7a378eb
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 20 deletions.
18 changes: 18 additions & 0 deletions src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ macro_rules! table {
fn from_clause(&self) -> String {
stringify!($name).to_string()
}

fn where_clause(&self) -> Option<(String, Vec<Option<Vec<u8>>>)> {
None
}
}

impl Table for table {
Expand Down Expand Up @@ -213,5 +217,19 @@ macro_rules! select_column_inner {
> for $parent::$column_name
{
}

impl<A, S, E> $crate::expression::SelectableExpression<
$crate::query_source::SelectSqlQuerySource<A, S, E>>
for $parent::$column_name where
$parent::$column_name: $crate::expression::SelectableExpression<S>,
{
}

impl<Source, Pred> $crate::expression::SelectableExpression<
$crate::query_source::FilteredQuerySource<Source, Pred>>
for $parent::$column_name where
$parent::$column_name: $crate::expression::SelectableExpression<Source>,
{
}
}
}
8 changes: 8 additions & 0 deletions src/query_source/joins.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ impl<Left, Right> QuerySource for InnerJoinSource<Left, Right> where
format!("{} INNER JOIN {} ON {}",
self.left.name(), self.right.name(), self.left.join_sql())
}

fn where_clause(&self) -> Option<(String, Vec<Option<Vec<u8>>>)> {
None
}
}

#[derive(Clone, Copy)]
Expand Down Expand Up @@ -61,6 +65,10 @@ impl<Left, Right> QuerySource for LeftOuterJoinSource<Left, Right> where
format!("{} LEFT OUTER JOIN {} ON {}",
self.left.name(), self.right.name(), self.left.join_sql())
}

fn where_clause(&self) -> Option<(String, Vec<Option<Vec<u8>>>)> {
None
}
}

pub trait JoinTo<T: Table>: Table {
Expand Down
21 changes: 9 additions & 12 deletions src/query_source/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ mod select;

use expression::{Expression, SelectableExpression, NonAggregate, SqlLiteral};
use expression::count::*;
use self::filter::FilteredQuerySource;
pub use self::filter::FilteredQuerySource;
pub use self::joins::{InnerJoinSource, LeftOuterJoinSource};
use self::select::SelectSqlQuerySource;
pub use self::select::SelectSqlQuerySource;
use std::convert::Into;
use types::{self, FromSqlRow, NativeSqlType};

Expand All @@ -23,10 +23,7 @@ pub trait QuerySource: Sized {

fn select_clause(&self) -> String;
fn from_clause(&self) -> String;

fn where_clause(&self) -> Option<(String, Vec<Option<Vec<u8>>>)> {
None
}
fn where_clause(&self) -> Option<(String, Vec<Option<Vec<u8>>>)>;

fn select<E, ST>(self, expr: E) -> SelectSqlQuerySource<ST, Self, E> where
SelectSqlQuerySource<ST, Self, E>: QuerySource,
Expand All @@ -52,6 +49,12 @@ pub trait QuerySource: Sized {
let sql = SqlLiteral::new(columns.into());
SelectSqlQuerySource::new(sql, self)
}

fn filter<T>(self, predicate: T) -> FilteredQuerySource<Self, T> where
T: SelectableExpression<Self, types::Bool>,
{
FilteredQuerySource::new(self, predicate)
}
}

pub trait Column {
Expand Down Expand Up @@ -95,10 +98,4 @@ pub trait Table: QuerySource {
{
LeftOuterJoinSource::new(self, other)
}

fn filter<T>(self, predicate: T) -> FilteredQuerySource<Self, T> where
T: SelectableExpression<Self, types::Bool>,
{
FilteredQuerySource::new(self, predicate)
}
}
4 changes: 4 additions & 0 deletions src/query_source/select.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,4 +35,8 @@ impl<A, S, E> QuerySource for SelectSqlQuerySource<A, S, E> where
fn from_clause(&self) -> String {
self.source.from_clause()
}

fn where_clause(&self) -> Option<(String, Vec<Option<Vec<u8>>>)> {
self.source.where_clause()
}
}
71 changes: 63 additions & 8 deletions tests/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,7 @@ use yaqb::*;
fn filter_by_int_equality() {
use schema::users::dsl::*;

let connection = connection();
setup_users_table(&connection);
let data = [NewUser::new("Sean", None), NewUser::new("Tess", None)];
connection.insert_without_return(&users, &data).unwrap();
let connection = connection_with_sean_and_tess_in_users_table();

let sean = User::new(1, "Sean");
let tess = User::new(2, "Tess");
Expand All @@ -21,14 +18,72 @@ fn filter_by_int_equality() {
fn filter_by_string_equality() {
use schema::users::dsl::*;

let connection = connection();
setup_users_table(&connection);
let data = [NewUser::new("Sean", None), NewUser::new("Tess", None)];
connection.insert_without_return(&users, &data).unwrap();
let connection = connection_with_sean_and_tess_in_users_table();

let sean = User::new(1, "Sean");
let tess = User::new(2, "Tess");
assert_eq!(Some(sean), connection.query_one(&users.filter(name.eq("Sean"))).unwrap());
assert_eq!(Some(tess), connection.query_one(&users.filter(name.eq("Tess"))).unwrap());
assert_eq!(None::<User>, connection.query_one(&users.filter(name.eq("Jim"))).unwrap());
}

#[test]
fn filter_after_joining() {
use schema::users::name;

let connection = connection_with_sean_and_tess_in_users_table();
setup_posts_table(&connection);
connection.execute("INSERT INTO POSTS (title, user_id) VALUES ('Hello', 1), ('World', 2)")
.unwrap();

let sean = User::new(1, "Sean");
let tess = User::new(2, "Tess");
let seans_post = Post::new(1, 1, "Hello", None);
let tess_post = Post::new(2, 2, "World", None);
let source = users::table.inner_join(posts::table);
assert_eq!(Some((sean, seans_post)),
connection.query_one(&source.filter(name.eq("Sean"))).unwrap());
assert_eq!(Some((tess, tess_post)),
connection.query_one(&source.filter(name.eq("Tess"))).unwrap());
assert_eq!(None::<(User, Post)>,
connection.query_one(&source.filter(name.eq("Jim"))).unwrap());
}

#[test]
fn select_then_filter() {
use schema::users::dsl::*;

let connection = connection_with_sean_and_tess_in_users_table();

let source = users.select(name);
assert_eq!(Some("Sean".to_string()),
connection.query_one(&source.filter(name.eq("Sean"))).unwrap());
assert_eq!(Some("Tess".to_string()),
connection.query_one(&source.filter(name.eq("Tess"))).unwrap());
assert_eq!(None::<String>, connection.query_one(&source.filter(name.eq("Jim"))).unwrap());
}

#[test]
fn filter_then_select() {
use schema::users::dsl::*;

let connection = connection();
setup_users_table(&connection);
let data = [NewUser::new("Sean", None), NewUser::new("Tess", None)];
connection.insert_without_return(&users, &data).unwrap();

assert_eq!(Some("Sean".to_string()),
connection.query_one(&users.filter(name.eq("Sean")).select(name)).unwrap());
assert_eq!(Some("Tess".to_string()),
connection.query_one(&users.filter(name.eq("Tess")).select(name)).unwrap());
assert_eq!(None::<String>, connection.query_one(&users.filter(name.eq("Jim")).select(name)).unwrap());
}

fn connection_with_sean_and_tess_in_users_table() -> Connection {
let connection = connection();
setup_users_table(&connection);
let data = [NewUser::new("Sean", None), NewUser::new("Tess", None)];
connection.insert_without_return(&users::table, &data).unwrap();

connection
}

0 comments on commit 7a378eb

Please sign in to comment.