Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support equality coercions, in particular in implicit joins #4025

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all 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
3 changes: 3 additions & 0 deletions diesel/src/expression/helper_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ pub type AsExprOf<Item, Type> = <Item as AsExpression<Type>>::Expression;
/// [`lhs.eq(rhs)`](crate::expression_methods::ExpressionMethods::eq())
pub type Eq<Lhs, Rhs> = Grouped<super::operators::Eq<Lhs, AsExpr<Rhs, Lhs>>>;

/// The return type of [`lhs.eq_coerce(rhs)`](crate::expression_methods::ExpressionMethods::eq_coerce())
pub type EqCoerce<Lhs, Rhs> = Grouped<super::operators::Eq<Lhs, Rhs>>;

/// The return type of
/// [`lhs.ne(rhs)`](crate::expression_methods::ExpressionMethods::ne())
pub type NotEq<Lhs, Rhs> = Grouped<super::operators::NotEq<Lhs, AsExpr<Rhs, Lhs>>>;
Expand Down
29 changes: 27 additions & 2 deletions diesel/src/expression/operators.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,11 +552,11 @@ postfix_operator!(
prefix_operator!(Not, " NOT ");

use crate::backend::{sql_dialect, Backend, SqlDialect};
use crate::expression::{TypedExpressionType, ValidGrouping};
use crate::expression::{Expression, TypedExpressionType, ValidGrouping};
use crate::insertable::{ColumnInsertValue, Insertable};
use crate::query_builder::{QueryFragment, QueryId, ValuesClause};
use crate::query_source::Column;
use crate::sql_types::{DieselNumericOps, SqlType};
use crate::sql_types::{self, DieselNumericOps, SqlType};

impl<T, U> Insertable<T::Table> for Eq<T, U>
where
Expand All @@ -581,6 +581,31 @@ where
}
}

impl<T, U> Eq<T, U> {
pub(crate) fn new_unchecked(left: T, right: U) -> super::grouped::Grouped<Self>
where
T: Expression,
U: Expression,
{
super::grouped::Grouped(Eq::new(left, right))
}
}
/// Marker trait representing that SQL supports the ` = ` operator for `Self` and T
///
/// It's used to typecheck [`.eq_coerce()`](crate::expression_methods::ExpressionMethods::eq_coerce())
pub trait CoerceEqual<ST> {}
impl<ST> CoerceEqual<ST> for ST {}
// All the types below work on all 3 supported backends.
// If adding more types here and they don't all work on all backends, it will be necessary to
// prevent coercions that don't work from compiling by restricting the QueryFragment impl on
// coercions that do work.
// If adding significantly more impls here, these impls should probably become a macro call
// so that it doesn't become too verbose.
impl CoerceEqual<sql_types::Int4> for sql_types::Int8 {}
impl CoerceEqual<sql_types::Int8> for sql_types::Int4 {}
impl CoerceEqual<sql_types::Nullable<sql_types::Int4>> for sql_types::Nullable<sql_types::Int8> {}
impl CoerceEqual<sql_types::Nullable<sql_types::Int8>> for sql_types::Nullable<sql_types::Int4> {}

/// This type represents a string concat operator
#[diesel_derives::__diesel_public_if(
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",
Expand Down
49 changes: 48 additions & 1 deletion diesel/src/expression_methods/global_expression_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,54 @@ pub trait ExpressionMethods: Expression + Sized {
Self::SqlType: SqlType,
T: AsExpression<Self::SqlType>,
{
Grouped(Eq::new(self, other.as_expression()))
Eq::new_unchecked(self, other.as_expression())
}

/// Creates a SQL ` = ` expression, letting differents types go through
/// where coercion is supported
///
/// With the regular [`.eq()`](ExpressionMethods::eq), the SQL types of the expression
/// must match exactly, and that allows writings of the form `column.eq(1_i32)` to work:
/// it knows that it should convert `1_i32` to an SQL expression of type
/// [`sql_types::Int4`](crate::sql_types::Int4) because `column` is an expression of SQL type
/// `Int4`.
///
/// However, that `.eq()` interface does not allow comparing an expression of type `Int4` to an
/// expression of type `Int8` for example.
/// When this is needed, the `.eq_coerce()` method can be used.
///
/// # Example
/// ```rust
/// # include!("../doctest_setup.rs");
/// #
/// # fn main() {
/// # run_test().unwrap();
/// # }
/// #
/// # fn run_test() -> QueryResult<()> {
/// # let connection = &mut establish_connection();
/// #
/// use diesel::sql_types;
///
/// let data = diesel::select((
/// 1_i32
/// .into_sql::<sql_types::Int4>()
/// .eq_coerce(1_i64.into_sql::<sql_types::Int8>()),
/// 1_i32
/// .into_sql::<sql_types::Int4>()
/// .eq_coerce(2_i64.into_sql::<sql_types::Int8>()),
/// ))
/// .first::<(bool, bool)>(connection);
/// assert_eq!(Ok((true, false)), data);
/// # Ok(())
/// # }
/// ```
fn eq_coerce<T>(self, other: T) -> dsl::EqCoerce<Self, T>
where
T: Expression,
Self::SqlType: CoerceEqual<T::SqlType>,
{
Eq::new_unchecked(self, other)
}

/// Creates a SQL `!=` expression.
Expand Down
6 changes: 4 additions & 2 deletions diesel/src/macros/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ macro_rules! joinable_inner {
) => {
impl $crate::JoinTo<$right_table_ty> for $left_table_ty {
type FromClause = $right_table_ty;
type OnClause = $crate::dsl::Eq<
type OnClause = $crate::dsl::EqCoerce<
$crate::internal::table_macro::NullableExpression<$foreign_key>,
$crate::internal::table_macro::NullableExpression<$primary_key_ty>,
>;
Expand All @@ -122,7 +122,9 @@ macro_rules! joinable_inner {

(
rhs,
$foreign_key.nullable().eq($primary_key_expr.nullable()),
$foreign_key
.nullable()
.eq_coerce($primary_key_expr.nullable()),
)
}
}
Expand Down
Loading