diff --git a/diesel/src/expression/helper_types.rs b/diesel/src/expression/helper_types.rs index 8c27e7bfd51f..348d88173f72 100644 --- a/diesel/src/expression/helper_types.rs +++ b/diesel/src/expression/helper_types.rs @@ -22,6 +22,9 @@ pub type AsExprOf = >::Expression; /// [`lhs.eq(rhs)`](crate::expression_methods::ExpressionMethods::eq()) pub type Eq = Grouped>>; +/// The return type of [`lhs.eq_coerce(rhs)`](crate::expression_methods::ExpressionMethods::eq_coerce()) +pub type EqCoerce = Grouped>; + /// The return type of /// [`lhs.ne(rhs)`](crate::expression_methods::ExpressionMethods::ne()) pub type NotEq = Grouped>>; diff --git a/diesel/src/expression/operators.rs b/diesel/src/expression/operators.rs index 836389908ae6..423bce7f1c8f 100644 --- a/diesel/src/expression/operators.rs +++ b/diesel/src/expression/operators.rs @@ -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 Insertable for Eq where @@ -581,6 +581,31 @@ where } } +impl Eq { + pub(crate) fn new_unchecked(left: T, right: U) -> super::grouped::Grouped + 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 {} +impl CoerceEqual 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 for sql_types::Int8 {} +impl CoerceEqual for sql_types::Int4 {} +impl CoerceEqual> for sql_types::Nullable {} +impl CoerceEqual> for sql_types::Nullable {} + /// This type represents a string concat operator #[diesel_derives::__diesel_public_if( feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes", diff --git a/diesel/src/expression_methods/global_expression_methods.rs b/diesel/src/expression_methods/global_expression_methods.rs index 893867a0bf8f..4faa8682ebdf 100644 --- a/diesel/src/expression_methods/global_expression_methods.rs +++ b/diesel/src/expression_methods/global_expression_methods.rs @@ -73,7 +73,54 @@ pub trait ExpressionMethods: Expression + Sized { Self::SqlType: SqlType, T: AsExpression, { - 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::() + /// .eq_coerce(1_i64.into_sql::()), + /// 1_i32 + /// .into_sql::() + /// .eq_coerce(2_i64.into_sql::()), + /// )) + /// .first::<(bool, bool)>(connection); + /// assert_eq!(Ok((true, false)), data); + /// # Ok(()) + /// # } + /// ``` + fn eq_coerce(self, other: T) -> dsl::EqCoerce + where + T: Expression, + Self::SqlType: CoerceEqual, + { + Eq::new_unchecked(self, other) } /// Creates a SQL `!=` expression. diff --git a/diesel/src/macros/mod.rs b/diesel/src/macros/mod.rs index 8290d0517398..af190ffa8676 100644 --- a/diesel/src/macros/mod.rs +++ b/diesel/src/macros/mod.rs @@ -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>, >; @@ -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()), ) } }