Add a general purpose NOT function

I was rather surprised that we didn't already have this. I didn't stick
this in `expression/predicates.rs`, as we didn't already have a macro
for prefix operators, and I want to refactor that to add it later. I may
move this to that macro in the future, and just have the return type be
`Not<Grouped<T::Expression>>` to reduce the amount of required code
here.

We need parenthesis in the resulting SQL to ensure that it always
applies to its arguments. `not(true.and(false))` should return `true`.
However, `NOT true AND false` is equivalent to `(NOT true) AND false`.

Fixes #944.
This commit is contained in:
Sean Griffin 2017-06-11 07:03:15 -04:00
parent f5fdd8cb0f
commit 2701c1c675
5 changed files with 116 additions and 4 deletions

View File

@ -4,6 +4,15 @@ All user visible changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/), as described
for Rust libraries in [RFC #1105](https://github.com/rust-lang/rfcs/blob/master/text/1105-api-evolution.md)
## Unreleased
### Added
* Added a function which maps to SQL `NOT`. See [the docs][not-0.14.0] for more
details.
[not-0.14.0]: http://docs.diesel.rs/diesel/expression/dsl/fn.not.html
## [0.13.0] - 2017-05-15
### Added

View File

@ -35,6 +35,8 @@ pub type Between<Lhs, Rhs> = super::predicates::Between<Lhs,
super::predicates::And<AsExpr<Rhs, Lhs>, AsExpr<Rhs, Lhs>>>;
pub type NotBetween<Lhs, Rhs> = super::predicates::NotBetween<Lhs,
super::predicates::And<AsExpr<Rhs, Lhs>, AsExpr<Rhs, Lhs>>>;
/// The return type of `not(expr)`
pub type Not<Expr> = super::not::Not<AsExprOf<Expr, types::Bool>>;
#[doc(inline)]
pub use super::predicates::{IsNull, IsNotNull, Asc, Desc};

View File

@ -34,6 +34,7 @@ pub mod functions;
pub mod grouped;
#[macro_use]
pub mod helper_types;
mod not;
#[doc(hidden)]
pub mod nullable;
#[doc(hidden)]
@ -47,11 +48,12 @@ mod unchecked_bind;
/// in functions where you need them.
pub mod dsl {
#[doc(inline)] pub use super::count::{count, count_star};
#[doc(inline)] pub use super::functions::date_and_time::*;
#[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;
#[doc(inline)] pub use super::functions::aggregate_folding::*;
#[doc(inline)] pub use super::functions::aggregate_ordering::*;
#[doc(inline)] pub use super::functions::date_and_time::*;
#[doc(inline)] pub use super::not::not;
#[doc(inline)] pub use super::sql_literal::sql;
#[cfg(feature = "postgres")]
pub use pg::expression::dsl::*;

View File

@ -0,0 +1,72 @@
use expression::*;
use query_builder::*;
use result::QueryResult;
use types::Bool;
/// Creates a SQL `NOT` expression
///
/// # Example
///
/// ```rust
/// # #[macro_use] extern crate diesel;
/// # include!("src/doctest_setup.rs");
/// #
/// # table! {
/// # users {
/// # id -> Integer,
/// # name -> VarChar,
/// # }
/// # }
/// #
/// # fn main() {
/// # use self::users::dsl::*;
/// # let connection = establish_connection();
/// use diesel::expression::not;
///
/// let users_with_name = users.select(id).filter(name.eq("Sean"));
/// let users_not_with_name = users.select(id).filter(
/// not(name.eq("Sean")));
///
/// assert_eq!(Ok(1), users_with_name.first(&connection));
/// assert_eq!(Ok(2), users_not_with_name.first(&connection));
/// # }
/// ```
pub fn not<T: AsExpression<Bool>>(expr: T) -> Not<T::Expression> {
Not(expr.as_expression())
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy)]
pub struct Not<T>(T);
impl<T: Expression<SqlType=Bool>> Expression for Not<T> {
type SqlType = Bool;
}
impl<T, QS> AppearsOnTable<QS> for Not<T> where
T: AppearsOnTable<QS>,
Not<T>: Expression,
{
}
impl<T, QS> SelectableExpression<QS> for Not<T> where
T: SelectableExpression<QS>,
Not<T>: AppearsOnTable<QS>,
{
}
impl<T: NonAggregate> NonAggregate for Not<T> {}
impl<T, DB> QueryFragment<DB> for Not<T> where
DB: Backend,
T: QueryFragment<DB>,
{
fn walk_ast(&self, mut out: AstPass<DB>) -> QueryResult<()> {
out.push_sql("NOT (");
self.0.walk_ast(out.reborrow())?;
out.push_sql(")");
Ok(())
}
}
impl_query_id!(Not<T>);

View File

@ -268,6 +268,33 @@ fn or_doesnt_mess_with_precidence_of_previous_statements() {
assert_eq!(Ok(0), count);
}
#[test]
fn not_does_not_affect_expressions_other_than_those_passed_to_it() {
use schema::users::dsl::*;
use diesel::expression::dsl::not;
let connection = connection_with_sean_and_tess_in_users_table();
let count = users.filter(not(name.eq("Tess")))
.filter(id.eq(1))
.count()
.get_result(&connection);
assert_eq!(Ok(1), count);
}
#[test]
fn not_affects_arguments_passed_when_they_contain_higher_operator_precedence() {
use schema::users::dsl::*;
use diesel::expression::dsl::not;
let connection = connection_with_sean_and_tess_in_users_table();
let count = users.filter(not(name.eq("Tess").and(id.eq(1))))
.count()
.get_result(&connection);
assert_eq!(Ok(2), count);
}
use diesel::types::VarChar;
sql_function!(lower, lower_t, (x: VarChar) -> VarChar);