Skip to content

Commit

Permalink
Add a function for SQL EXISTS expressions.
Browse files Browse the repository at this point in the history
While this is useful for some cases where you don't want to load the
rows, this won't fill every use case for the expression, as right now
you wouldn't be able to build a query that references the outer table.
For us to do that and have it be type safe we'd need overlapping impls
for `SelectableExpression` (story of my life), which requires
rust-lang/rust#29864 being implemented.

Fixes #414.
  • Loading branch information
sgrif committed Dec 8, 2016
1 parent 47d1196 commit 4d0f783
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 0 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/

[insert]: http://docs.diesel.rs/diesel/fn.insert.html

* Added a function for SQL `EXISTS` expressions. See
[`diesel::expression::dsl::exists`][exists] for details.

[exists]: http://docs.diesel.rs/diesel/expression/dsl/fn.sql.html

### Changed

* All macros with the same name as traits we can derive (e.g. `Queryable!`) have
Expand Down
79 changes: 79 additions & 0 deletions diesel/src/expression/exists.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use backend::Backend;
use expression::{Expression, SelectableExpression, NonAggregate};
use query_builder::*;
use result::QueryResult;
use types::Bool;

/// Creates a SQL `EXISTS` expression. The argument must be a complete SQL
/// query. The result of this could in theory be passed to `.filter`, but since
/// the query cannot reference columns from the outer query, this is of limited
/// usefulness.
///
/// # Example
/// ```rust
/// # #[macro_use] extern crate diesel;
/// # include!("src/doctest_setup.rs");
/// #
/// # table! {
/// # users {
/// # id -> Integer,
/// # name -> VarChar,
/// # }
/// # }
/// #
/// # fn main() {
/// # use self::users::dsl::*;
/// # use diesel::select;
/// # use diesel::expression::dsl::exists;
/// # let connection = establish_connection();
/// let sean_exists = select(exists(users.filter(name.eq("Sean"))))
/// .get_result(&connection);
/// let jim_exists = select(exists(users.filter(name.eq("Jim"))))
/// .get_result(&connection);
/// assert_eq!(Ok(true), sean_exists);
/// assert_eq!(Ok(false), jim_exists);
/// # }
/// ```
pub fn exists<T: AsQuery>(query: T) -> Exists<T::Query> {
Exists(query.as_query())
}

#[derive(Debug, Clone, Copy)]
pub struct Exists<T>(T);

impl<T> Expression for Exists<T> where
T: Query,
{
type SqlType = Bool;
}

impl<T, QS> SelectableExpression<QS> for Exists<T> where
Exists<T>: Expression,
{
}

impl<T> NonAggregate for Exists<T> {
}

impl<T, DB> QueryFragment<DB> for Exists<T> where
DB: Backend,
T: QueryFragment<DB>,
{
fn to_sql(&self, out: &mut DB::QueryBuilder) -> BuildQueryResult {
out.push_sql("EXISTS (");
try!(self.0.to_sql(out));
out.push_sql(")");
Ok(())
}

fn collect_binds(&self, out: &mut DB::BindCollector) -> QueryResult<()> {
try!(self.0.collect_binds(out));
Ok(())
}

fn is_safe_to_cache_prepared(&self) -> bool {
self.0.is_safe_to_cache_prepared()
}
}

impl_query_id!(Exists<T>);
3 changes: 3 additions & 0 deletions diesel/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pub mod array_comparison;
pub mod bound;
#[doc(hidden)]
pub mod count;
#[doc(hidden)]
pub mod exists;
pub mod expression_methods;
#[doc(hidden)]
pub mod functions;
Expand All @@ -47,6 +49,7 @@ pub mod dsl {
#[doc(inline)] pub use super::functions::aggregate_ordering::*;
#[doc(inline)] pub use super::functions::aggregate_folding::*;
#[doc(inline)] pub use super::sql_literal::sql;
#[doc(inline)] pub use super::exists::exists;

#[cfg(feature = "postgres")]
pub use pg::expression::dsl::*;
Expand Down

0 comments on commit 4d0f783

Please sign in to comment.