Skip to content

Commit f86e9ed

Browse files
committed
Support equality coercions, in particular in implicit joins
1 parent 14ac1ec commit f86e9ed

File tree

4 files changed

+82
-5
lines changed

4 files changed

+82
-5
lines changed

diesel/src/expression/helper_types.rs

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ pub type AsExprOf<Item, Type> = <Item as AsExpression<Type>>::Expression;
2222
/// [`lhs.eq(rhs)`](crate::expression_methods::ExpressionMethods::eq())
2323
pub type Eq<Lhs, Rhs> = Grouped<super::operators::Eq<Lhs, AsExpr<Rhs, Lhs>>>;
2424

25+
/// The return type of [`lhs.eq_coerce(rhs)`](crate::expression_methods::ExpressionMethods::eq_coerce())
26+
pub type EqCoerce<Lhs, Rhs> = Grouped<super::operators::Eq<Lhs, Rhs>>;
27+
2528
/// The return type of
2629
/// [`lhs.ne(rhs)`](crate::expression_methods::ExpressionMethods::ne())
2730
pub type NotEq<Lhs, Rhs> = Grouped<super::operators::NotEq<Lhs, AsExpr<Rhs, Lhs>>>;

diesel/src/expression/operators.rs

+27-2
Original file line numberDiff line numberDiff line change
@@ -552,11 +552,11 @@ postfix_operator!(
552552
prefix_operator!(Not, " NOT ");
553553

554554
use crate::backend::{sql_dialect, Backend, SqlDialect};
555-
use crate::expression::{TypedExpressionType, ValidGrouping};
555+
use crate::expression::{Expression, TypedExpressionType, ValidGrouping};
556556
use crate::insertable::{ColumnInsertValue, Insertable};
557557
use crate::query_builder::{QueryFragment, QueryId, ValuesClause};
558558
use crate::query_source::Column;
559-
use crate::sql_types::{DieselNumericOps, SqlType};
559+
use crate::sql_types::{self, DieselNumericOps, SqlType};
560560

561561
impl<T, U> Insertable<T::Table> for Eq<T, U>
562562
where
@@ -581,6 +581,31 @@ where
581581
}
582582
}
583583

584+
impl<T, U> Eq<T, U> {
585+
pub(crate) fn new_unchecked(left: T, right: U) -> super::grouped::Grouped<Self>
586+
where
587+
T: Expression,
588+
U: Expression,
589+
{
590+
super::grouped::Grouped(Eq::new(left, right))
591+
}
592+
}
593+
/// Marker trait representing that SQL supports the ` = ` operator for `Self` and T
594+
///
595+
/// It's used to typecheck [`.eq_coerce()`](crate::expression_methods::ExpressionMethods::eq_coerce())
596+
pub trait CoerceEqual<ST> {}
597+
impl<ST> CoerceEqual<ST> for ST {}
598+
// All the types below work on all 3 supported backends.
599+
// If adding more types here and they don't all work on all backends, it will be necessary to
600+
// prevent coercions that don't work from compiling by restricting the QueryFragment impl on
601+
// coercions that do work.
602+
// If adding significantly more impls here, these impls should probably become a macro call
603+
// so that it doesn't become too verbose.
604+
impl CoerceEqual<sql_types::Int4> for sql_types::Int8 {}
605+
impl CoerceEqual<sql_types::Int8> for sql_types::Int4 {}
606+
impl CoerceEqual<sql_types::Nullable<sql_types::Int4>> for sql_types::Nullable<sql_types::Int8> {}
607+
impl CoerceEqual<sql_types::Nullable<sql_types::Int8>> for sql_types::Nullable<sql_types::Int4> {}
608+
584609
/// This type represents a string concat operator
585610
#[diesel_derives::__diesel_public_if(
586611
feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes",

diesel/src/expression_methods/global_expression_methods.rs

+48-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,54 @@ pub trait ExpressionMethods: Expression + Sized {
7373
Self::SqlType: SqlType,
7474
T: AsExpression<Self::SqlType>,
7575
{
76-
Grouped(Eq::new(self, other.as_expression()))
76+
Eq::new_unchecked(self, other.as_expression())
77+
}
78+
79+
/// Creates a SQL ` = ` expression, letting differents types go through
80+
/// where coercion is supported
81+
///
82+
/// With the regular [`.eq()`](ExpressionMethods::eq), the SQL types of the expression
83+
/// must match exactly, and that allows writings of the form `column.eq(1_i32)` to work:
84+
/// it knows that it should convert `1_i32` to an SQL expression of type
85+
/// [`sql_types::Int4`](crate::sql_types::Int4) because `column` is an expression of SQL type
86+
/// `Int4`.
87+
///
88+
/// However, that `.eq()` interface does not allow comparing an expression of type `Int4` to an
89+
/// expression of type `Int8` for example.
90+
/// When this is needed, the `.eq_coerce()` method can be used.
91+
///
92+
/// # Example
93+
/// ```rust
94+
/// # include!("../doctest_setup.rs");
95+
/// #
96+
/// # fn main() {
97+
/// # run_test().unwrap();
98+
/// # }
99+
/// #
100+
/// # fn run_test() -> QueryResult<()> {
101+
/// # let connection = &mut establish_connection();
102+
/// #
103+
/// use diesel::sql_types;
104+
///
105+
/// let data = dsl::select((
106+
/// 1_i32
107+
/// .into_sql::<sql_types::Int4>()
108+
/// .coerce_eq(1_i64.into_sql::<sql_types::Int8>()),
109+
/// 1_i32
110+
/// .into_sql::<sql_types::Int4>()
111+
/// .coerce_eq(2_i64.into_sql::<sql_types::Int8>()),
112+
/// ))
113+
/// .first::<(bool, bool)>(connection);
114+
/// assert_eq!(Ok((true, false)), data);
115+
/// # Ok(())
116+
/// # }
117+
/// ```
118+
fn eq_coerce<T>(self, other: T) -> dsl::EqCoerce<Self, T>
119+
where
120+
T: Expression,
121+
Self::SqlType: CoerceEqual<T::SqlType>,
122+
{
123+
Eq::new_unchecked(self, other)
77124
}
78125

79126
/// Creates a SQL `!=` expression.

diesel/src/macros/mod.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ macro_rules! joinable_inner {
112112
) => {
113113
impl $crate::JoinTo<$right_table_ty> for $left_table_ty {
114114
type FromClause = $right_table_ty;
115-
type OnClause = $crate::dsl::Eq<
115+
type OnClause = $crate::dsl::EqCoerce<
116116
$crate::internal::table_macro::NullableExpression<$foreign_key>,
117117
$crate::internal::table_macro::NullableExpression<$primary_key_ty>,
118118
>;
@@ -122,7 +122,9 @@ macro_rules! joinable_inner {
122122

123123
(
124124
rhs,
125-
$foreign_key.nullable().eq($primary_key_expr.nullable()),
125+
$foreign_key
126+
.nullable()
127+
.eq_coerce($primary_key_expr.nullable()),
126128
)
127129
}
128130
}

0 commit comments

Comments
 (0)