Skip to content

Commit

Permalink
Support equality coercions, in particular in implicit joins
Browse files Browse the repository at this point in the history
  • Loading branch information
Ten0 committed May 14, 2024
1 parent 14ac1ec commit dbd8ed4
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 5 deletions.
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

0 comments on commit dbd8ed4

Please sign in to comment.