Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions diesel/src/expression/count.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use super::{Expression, ValidGrouping};
use crate::backend::Backend;
use crate::query_builder::*;
use crate::result::QueryResult;
use crate::sql_types::{BigInt, DieselNumericOps, Nullable, SingleValue, SqlType};
use crate::sql_types::{BigInt, DieselNumericOps, SingleValue, SqlType};

sql_function! {
/// Creates a SQL `COUNT` expression
Expand All @@ -25,7 +25,7 @@ sql_function! {
/// # }
/// ```
#[aggregate]
fn count<T: SqlType + SingleValue>(expr: Nullable<T>) -> BigInt;
fn count<T: SqlType + SingleValue>(expr: T) -> BigInt;
}

/// Creates a SQL `COUNT(*)` expression
Expand Down
6 changes: 3 additions & 3 deletions diesel/src/expression/functions/aggregate_folding.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use crate::expression::functions::sql_function;
use crate::sql_types::{Foldable, Nullable};
use crate::sql_types::Foldable;

sql_function! {
/// Represents a SQL `SUM` function. This function can only take types which are
Expand All @@ -18,7 +18,7 @@ sql_function! {
/// # }
/// ```
#[aggregate]
fn sum<ST: Foldable>(expr: Nullable<ST>) -> ST::Sum;
fn sum<ST: Foldable>(expr: ST) -> ST::Sum;
}

sql_function! {
Expand Down Expand Up @@ -64,5 +64,5 @@ sql_function! {
/// # Ok(())
/// # }
#[aggregate]
fn avg<ST: Foldable>(expr: Nullable<ST>) -> ST::Avg;
fn avg<ST: Foldable>(expr: ST) -> ST::Avg;
}
14 changes: 7 additions & 7 deletions diesel/src/expression/functions/aggregate_ordering.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
use crate::expression::functions::sql_function;
use crate::sql_types::{IntoNullable, Nullable, SingleValue, SqlOrd, SqlType};
use crate::sql_types::{IntoNullable, SingleValue, SqlOrd, SqlType};

pub trait SqlOrdAggregate: SingleValue {
type Ret: SqlType + SingleValue;
}

impl<ST> SqlOrdAggregate for ST
impl<T> SqlOrdAggregate for T
where
ST: SqlOrd + SingleValue + IntoNullable,
ST::Nullable: SingleValue,
T: SqlOrd + IntoNullable + SingleValue,
T::Nullable: SqlType + SingleValue,
{
type Ret = <Self as IntoNullable>::Nullable;
type Ret = T::Nullable;
}

sql_function! {
Expand All @@ -29,7 +29,7 @@ sql_function! {
/// assert_eq!(Ok(Some(8)), animals.select(max(legs)).first(&connection));
/// # }
#[aggregate]
fn max<ST: SqlOrdAggregate>(expr: Nullable<ST>) -> ST::Ret;
fn max<ST: SqlOrdAggregate>(expr: ST) -> ST::Ret;
}

sql_function! {
Expand All @@ -48,5 +48,5 @@ sql_function! {
/// assert_eq!(Ok(Some(4)), animals.select(min(legs)).first(&connection));
/// # }
#[aggregate]
fn min<ST: SqlOrdAggregate>(expr: Nullable<ST>) -> ST::Ret;
fn min<ST: SqlOrdAggregate>(expr: ST) -> ST::Ret;
}
21 changes: 18 additions & 3 deletions diesel/src/expression/functions/date_and_time.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
use crate::backend::Backend;
use crate::expression::coerce::Coerce;
use crate::expression::functions::sql_function;
#[cfg(feature = "postgres")]
use crate::expression::{coerce::Coerce, AsExpression};
use crate::expression::{Expression, ValidGrouping};
use crate::expression::{AsExpression, Expression, ValidGrouping};
use crate::query_builder::*;
use crate::result::QueryResult;
use crate::sql_types::*;
Expand Down Expand Up @@ -47,6 +46,14 @@ sql_function! {
fn date(expr: Timestamp) -> Date;
}

impl AsExpression<Nullable<Timestamp>> for now {
type Expression = Coerce<now, Nullable<Timestamp>>;

fn as_expression(self) -> Self::Expression {
Coerce::new(self)
}
}

#[cfg(feature = "postgres")]
impl AsExpression<Timestamptz> for now {
type Expression = Coerce<now, Timestamptz>;
Expand Down Expand Up @@ -85,3 +92,11 @@ impl_selectable_expression!(today);

operator_allowed!(today, Add, add);
operator_allowed!(today, Sub, sub);

impl AsExpression<Nullable<Date>> for today {
type Expression = Coerce<today, Nullable<Date>>;

fn as_expression(self) -> Self::Expression {
Coerce::new(self)
}
}
4 changes: 2 additions & 2 deletions diesel/src/expression/functions/helper_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
use crate::dsl::{AsExprOf, SqlTypeOf};
use crate::expression::grouped::Grouped;
use crate::expression::operators;
use crate::sql_types::{Bool, Nullable};
use crate::sql_types::Bool;

/// The return type of [`not(expr)`](../dsl/fn.not.html)
pub type not<Expr> = operators::Not<Grouped<AsExprOf<Expr, Nullable<Bool>>>>;
pub type not<Expr> = operators::Not<Grouped<AsExprOf<Expr, Bool>>>;

/// The return type of [`max(expr)`](../dsl/fn.max.html)
pub type max<Expr> = super::aggregate_ordering::max::HelperType<SqlTypeOf<Expr>, Expr>;
Expand Down
4 changes: 2 additions & 2 deletions diesel/src/expression/helper_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,11 @@ pub type Nullable<Expr> = super::nullable::Nullable<Expr>;

/// The return type of
/// [`lhs.and(rhs)`](../expression_methods/trait.BoolExpressionMethods.html#method.and)
pub type And<Lhs, Rhs> = Grouped<super::operators::And<Nullable<Lhs>, AsExpr<Rhs, Nullable<Lhs>>>>;
pub type And<Lhs, Rhs, ST = sql_types::Bool> = Grouped<super::operators::And<Lhs, AsExprOf<Rhs, ST>>>;

/// The return type of
/// [`lhs.or(rhs)`](../expression_methods/trait.BoolExpressionMethods.html#method.or)
pub type Or<Lhs, Rhs> = Grouped<super::operators::Or<Lhs, AsExpr<Rhs, Nullable<Lhs>>>>;
pub type Or<Lhs, Rhs, ST = sql_types::Bool> = Grouped<super::operators::Or<Lhs, AsExprOf<Rhs, ST>>>;

/// The return type of
/// [`lhs.escape('x')`](../expression_methods/trait.EscapeExpressionMethods.html#method.escape)
Expand Down
99 changes: 6 additions & 93 deletions diesel/src/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ pub use self::sql_literal::{SqlLiteral, UncheckedBind};

use crate::backend::Backend;
use crate::dsl::AsExprOf;
use crate::sql_types::{HasSqlType, IntoNotNullable, SingleValue, SqlType};
use crate::sql_types::{HasSqlType, SingleValue, SqlType};

/// Represents a typed fragment of SQL.
///
Expand Down Expand Up @@ -208,101 +208,14 @@ pub use diesel_derives::AsExpression;

impl<T, ST> AsExpression<ST> for T
where
ST: SqlType + IntoNotNullable + TypedExpressionType,
ST::NotNullable: SingleValue,
self::as_expression_impl::ExpressionImplHelper<T, ST::IsNull, <T::SqlType as SqlType>::IsNull>:
self::as_expression_impl::AsExpressionHelper<ST>,
T: Expression,
T::SqlType: SqlType,
T: Expression<SqlType = ST>,
ST: SqlType + TypedExpressionType,
{
type Expression = <self::as_expression_impl::ExpressionImplHelper<
T,
ST::IsNull,
<T::SqlType as SqlType>::IsNull,
> as self::as_expression_impl::AsExpressionHelper<ST>>::Expression;

fn as_expression(self) -> Self::Expression {
use self::as_expression_impl::AsExpressionHelper;

let t = self::as_expression_impl::ExpressionImplHelper::<
_,
ST::IsNull,
<T::SqlType as SqlType>::IsNull,
>(self, std::marker::PhantomData);
t.as_expression()
}
}

mod as_expression_impl {
use super::*;
use crate::sql_types::is_nullable;
type Expression = Self;

#[allow(missing_debug_implementations)]
pub struct ExpressionImplHelper<T, IsNullExpr, IsNullAsExpr>(
pub T,
pub std::marker::PhantomData<(IsNullExpr, IsNullAsExpr)>,
);

// We could use `AsExpression` here instead of defining a new trait in theory
// in practice we hit https://github.com/rust-lang/rust/issues/77446 then
// when defining a custom type in a third party crate
pub trait AsExpressionHelper<ST: TypedExpressionType> {
type Expression: Expression<SqlType = ST>;

fn as_expression(self) -> Self::Expression;
fn as_expression(self) -> Self {
self
}

// This impl is for accepting a not nullable expression in a position where
// a not nullable expression is expected
impl<T, ST> AsExpressionHelper<ST>
for ExpressionImplHelper<T, is_nullable::NotNull, is_nullable::NotNull>
where
ST: SqlType<IsNull = is_nullable::NotNull> + TypedExpressionType,
T: Expression<SqlType = ST>,
{
type Expression = T;

fn as_expression(self) -> Self::Expression {
self.0
}
}

// This impl is for accepting a not nullable expression in a position where
// a nullable expression is expected
impl<T, ST> AsExpressionHelper<ST>
for ExpressionImplHelper<T, is_nullable::IsNullable, is_nullable::NotNull>
where
ST: SqlType<IsNull = is_nullable::IsNullable> + IntoNotNullable + TypedExpressionType,
ST::NotNullable: TypedExpressionType + SqlType,
T: Expression<SqlType = ST::NotNullable>,
super::nullable::Nullable<T>: Expression<SqlType = ST>,
{
type Expression = super::nullable::Nullable<T>;

fn as_expression(self) -> Self::Expression {
super::nullable::Nullable::new(self.0)
}
}

// This impl is for accepting a nullable expression in a position where
// a nullable expression is expected
impl<T, ST> AsExpressionHelper<ST>
for ExpressionImplHelper<T, is_nullable::IsNullable, is_nullable::IsNullable>
where
ST: SqlType<IsNull = is_nullable::IsNullable> + TypedExpressionType,
T: Expression<SqlType = ST>,
{
type Expression = T;

fn as_expression(self) -> Self::Expression {
self.0
}
}

// impl<T, ST> AsExpressionHelper<ST> for
// ExpressionImplHelper<T, is_nullable::NotNull, is_nullable::IsNullable>
// is missing because we don't want to accept a nullable expression in possition where
// where a not nullable expression is expected
}

/// Converts a type to its representation for use in Diesel's query builder.
Expand Down
4 changes: 2 additions & 2 deletions diesel/src/expression/not.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::expression::grouped::Grouped;
use crate::expression::AsExpression;
use crate::helper_types::not;
use crate::sql_types::{Bool, Nullable};
use crate::sql_types::Bool;

/// Creates a SQL `NOT` expression
///
Expand All @@ -25,7 +25,7 @@ use crate::sql_types::{Bool, Nullable};
/// ```
pub fn not<T>(expr: T) -> not<T>
where
T: AsExpression<Nullable<Bool>>,
T: AsExpression<Bool>,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if that should only accept non nullable values. I think accepting both Bool and Nullable<Bool> here is more meaningful. On the other hand that could potentially lead to situations where rustc cannot infer the correct type for ST from AsExpression here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm that looks like kind of the same problem as and/or no? Where if we enforce that it takes an AsExpression<Nullable<Bool>> we'll never be able to make it return a non-nullable bool?

Additionally, not all expressions of SqlType Bool implement AsExpression<Nullable<Bool>> anymore, because otherwise this creates type inference issues with and/or: #2597 (comment)
Hence the following does not compile anymore if we do this: not(name.eq("Sean") (from tests)

Finally, I believe the behavior of not on Nullable<Bool> is a bit of a trap in that NULL values result in NULL, so they do not change "truthiness" with regards to e.g. a filter, which may lead to nasty bugs in case this kind of confusion is made and the compiler does not prevent it.

So overall I believe we should keep this behavior where we only accept non-nullable values here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think accepting both Bool and Nullable

I had encountered a situation where I want to store the same. Is there a way we can specify that?

Copy link
Member

@weiznich weiznich Jan 7, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We must accept both variants (Bool and Nullable<Bool>) here otherwise it's not possible to write queries like not(foo.and(bar)) or even not(foo.eq(bar)) anymore in cases one of the arguments is Nullable<Bool>

Edit: @pksunkara Seems like you've added your comment while I typed mine. Yes we need this method to automatically accept both variants, otherwise we would restrict quite a lot of possible queries using not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had encountered a situation where I want to store the same

Not sure what you mean by "the same".

We must accept both variants (Bool and Nullable<Bool>) here otherwise it's not possible to write queries like not(foo.and(bar)) or even not(foo.eq(bar)) anymore in cases one of the arguments is Nullable<Bool>

So I thought it maybe wasn't a good idea to have not support this implicitly, as this may easily lead to nasty bugs:

I believe the behavior of not on Nullable<Bool> is a bit of a trap in that NULL values result in NULL, so they do not change "truthiness" with regards to e.g. a filter, which may lead to nasty bugs in case this kind of confusion is made and the compiler does not prevent it.

Example:

table.filter(not(foo.eq(bar)))

Where foo = true and bar = NULL, the lines won't show, because not won't change the "truthiness" of the expression, which is a bit counter-intuitive.

Also, our codebase is rather large and we have zero such patterns.

Given that, do you still believe it's important for not to support this? (and we couldn't be happy with a separate nullable_not?)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still believe that not should support that as this is just the behavior of the underlying SQL operator. I feel that we had this discussion already about a few operators (for example eq in another context). Every time the final answer was: We follow the semantic of the underlying SQL operator as we do not try to hide complexity at this level of abstraction. Hiding this complexity can be done in a crate build on top of diesel, providing it owns set of operators that is not based on SQL in my opinion.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree. Done in df8b5e5.

{
super::operators::Not::new(Grouped(expr.as_expression()))
}
6 changes: 1 addition & 5 deletions diesel/src/expression/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -446,11 +446,7 @@ postfix_operator!(
crate::expression::expression_types::NotSelectable
);

prefix_operator!(
Not,
" NOT ",
crate::sql_types::Nullable<crate::sql_types::Bool>
);
prefix_operator!(Not, " NOT ");

use crate::backend::Backend;
use crate::expression::{TypedExpressionType, ValidGrouping};
Expand Down
25 changes: 11 additions & 14 deletions diesel/src/expression_methods/bool_expression_methods.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
use crate::dsl;
use crate::expression::grouped::Grouped;
use crate::expression::operators::{And, Or};
use crate::expression::{AsExpression, Expression};
use crate::sql_types::{BoolOrNullableBool, IntoNullable, SingleValue, SqlType};
use crate::expression::{AsExpression, Expression, TypedExpressionType};
use crate::sql_types::{BoolOrNullableBool, SqlType};

/// Methods present on boolean expressions
pub trait BoolExpressionMethods: Expression + Sized {
Expand Down Expand Up @@ -37,17 +37,14 @@ pub trait BoolExpressionMethods: Expression + Sized {
/// assert_eq!(expected, data);
/// # Ok(())
/// # }
fn and<T>(self, other: T) -> dsl::And<Self, T>
fn and<T, ST>(self, other: T) -> dsl::And<Self, T, ST>
where
Self::SqlType: SqlType + IntoNullable,
<Self::SqlType as IntoNullable>::Nullable: SingleValue,
T: AsExpression<<Self::SqlType as IntoNullable>::Nullable>,
Self::SqlType: SqlType,
ST: SqlType + TypedExpressionType,
T: AsExpression<ST>,
And<Self, T::Expression>: Expression,
{
Grouped(And::new(
crate::expression::nullable::Nullable::new(self),
other.as_expression(),
))
Grouped(And::new(self, other.as_expression()))
}

/// Creates a SQL `OR` expression
Expand Down Expand Up @@ -87,11 +84,11 @@ pub trait BoolExpressionMethods: Expression + Sized {
/// assert_eq!(expected, data);
/// # Ok(())
/// # }
fn or<T>(self, other: T) -> dsl::Or<Self, T>
fn or<T, ST>(self, other: T) -> dsl::Or<Self, T, ST>
where
Self::SqlType: SqlType + IntoNullable,
<Self::SqlType as IntoNullable>::Nullable: SingleValue,
T: AsExpression<<Self::SqlType as IntoNullable>::Nullable>,
Self::SqlType: SqlType,
ST: SqlType + TypedExpressionType,
T: AsExpression<ST>,
Or<Self, T::Expression>: Expression,
{
Grouped(Or::new(self, other.as_expression()))
Expand Down
7 changes: 3 additions & 4 deletions diesel/src/expression_methods/eq_all.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,15 @@
use crate::expression::grouped::Grouped;
use crate::expression::nullable::Nullable;
use crate::expression::operators::And;
use crate::expression::Expression;
use crate::expression_methods::*;
use crate::sql_types::{self, Bool};
use crate::sql_types::Bool;

/// This method is used by `FindDsl` to work with tuples. Because we cannot
/// express this without specialization or overlapping impls, it is brute force
/// implemented on columns in the `column!` macro.
#[doc(hidden)]
pub trait EqAll<Rhs> {
type Output: Expression<SqlType = sql_types::Nullable<Bool>>;
type Output: Expression<SqlType = Bool>;

fn eq_all(self, rhs: Rhs) -> Self::Output;
}
Expand All @@ -29,7 +28,7 @@ macro_rules! impl_eq_all {
($($Left,)+): EqAll<($($Right,)+)>,
{
type Output = Grouped<And<
Nullable<<$Left1 as EqAll<$Right1>>::Output>,
<$Left1 as EqAll<$Right1>>::Output,
<($($Left,)+) as EqAll<($($Right,)+)>>::Output,
>>;

Expand Down
9 changes: 3 additions & 6 deletions diesel/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -109,16 +109,13 @@ macro_rules! __diesel_column {

impl<T> $crate::EqAll<T> for $column_name where
T: $crate::expression::AsExpression<$($Type)*>,
$crate::dsl::Nullable<$crate::dsl::Eq<$column_name, T::Expression>>:
$crate::Expression<SqlType = $crate::sql_types::Nullable<$crate::sql_types::Bool>>,
$crate::dsl::Eq<$column_name, T::Expression>: $crate::Expression<SqlType=$crate::sql_types::Bool>,
{
type Output = $crate::dsl::Nullable<$crate::dsl::Eq<Self, T::Expression>>;
type Output = $crate::dsl::Eq<Self, T::Expression>;

fn eq_all(self, rhs: T) -> Self::Output {
use $crate::expression_methods::ExpressionMethods;
use $crate::expression_methods::NullableExpressionMethods;

self.eq(rhs).nullable()
self.eq(rhs)
}
}

Expand Down
Loading