Skip to content

Commit 8939fce

Browse files
authored
Merge pull request #4353 from Ten0/array_subselect
`.eq_any(ARRAY(subselect))` and `.eq_any(ARRAY[..., ...])` support
2 parents 6e76f04 + 51e4f32 commit 8939fce

File tree

12 files changed

+444
-77
lines changed

12 files changed

+444
-77
lines changed

diesel/src/expression/array_comparison.rs

+31-16
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
//! This module contains the query dsl node definitions
22
//! for array comparison operations like `IN` and `NOT IN`
33
4+
use super::expression_types::NotSelectable;
45
use crate::backend::{sql_dialect, Backend, SqlDialect};
56
use crate::expression::subselect::Subselect;
67
use crate::expression::{
@@ -71,7 +72,7 @@ impl<T, U> NotIn<T, U> {
7172
impl<T, U> Expression for In<T, U>
7273
where
7374
T: Expression,
74-
U: Expression<SqlType = T::SqlType>,
75+
U: InExpression<SqlType = T::SqlType>,
7576
T::SqlType: SqlType,
7677
sql_types::is_nullable::IsSqlTypeNullable<T::SqlType>:
7778
sql_types::MaybeNullableType<sql_types::Bool>,
@@ -85,7 +86,7 @@ where
8586
impl<T, U> Expression for NotIn<T, U>
8687
where
8788
T: Expression,
88-
U: Expression<SqlType = T::SqlType>,
89+
U: InExpression<SqlType = T::SqlType>,
8990
T::SqlType: SqlType,
9091
sql_types::is_nullable::IsSqlTypeNullable<T::SqlType>:
9192
sql_types::MaybeNullableType<sql_types::Bool>,
@@ -111,7 +112,7 @@ where
111112
DB: Backend
112113
+ SqlDialect<ArrayComparison = sql_dialect::array_comparison::AnsiSqlArrayComparison>,
113114
T: QueryFragment<DB>,
114-
U: QueryFragment<DB> + MaybeEmpty,
115+
U: QueryFragment<DB> + InExpression,
115116
{
116117
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
117118
if self.values.is_empty() {
@@ -142,7 +143,7 @@ where
142143
DB: Backend
143144
+ SqlDialect<ArrayComparison = sql_dialect::array_comparison::AnsiSqlArrayComparison>,
144145
T: QueryFragment<DB>,
145-
U: QueryFragment<DB> + MaybeEmpty,
146+
U: QueryFragment<DB> + InExpression,
146147
{
147148
fn walk_ast<'b>(&'b self, mut out: AstPass<'_, 'b, DB>) -> QueryResult<()> {
148149
if self.values.is_empty() {
@@ -176,9 +177,9 @@ impl_selectable_expression!(NotIn<T, U>);
176177
/// This trait is exposed for custom third party backends so
177178
/// that they can restrict the [`QueryFragment`] implementations
178179
/// for [`In`] and [`NotIn`].
179-
pub trait AsInExpression<T: SqlType + TypedExpressionType> {
180+
pub trait AsInExpression<T: SqlType> {
180181
/// Type of the expression returned by [AsInExpression::as_in_expression]
181-
type InExpression: MaybeEmpty + Expression<SqlType = T>;
182+
type InExpression: InExpression<SqlType = T>;
182183

183184
/// Construct the diesel query dsl representation of
184185
/// the `IN (values)` clause for the given type
@@ -204,9 +205,15 @@ where
204205
}
205206
}
206207

207-
/// A helper trait to check if the values clause of
208-
/// an [`In`] or [`NotIn`] query dsl node is empty or not
209-
pub trait MaybeEmpty {
208+
/// A marker trait that identifies query fragments that can be used in `IN(...)` and `NOT IN(...)`
209+
/// clauses, (or `= ANY (...)` clauses on the Postgres backend)
210+
///
211+
/// These can be wrapped in [`In`] or [`NotIn`] query dsl nodes
212+
pub trait InExpression {
213+
/// The SQL type of the inner values, which should be the same as the left of the `IN` or
214+
/// `NOT IN` clause
215+
type SqlType: SqlType;
216+
210217
/// Returns `true` if self represents an empty collection
211218
/// Otherwise `false` is returned.
212219
fn is_empty(&self) -> bool;
@@ -215,7 +222,7 @@ pub trait MaybeEmpty {
215222
impl<ST, F, S, D, W, O, LOf, G, H, LC> AsInExpression<ST>
216223
for SelectStatement<F, S, D, W, O, LOf, G, H, LC>
217224
where
218-
ST: SqlType + TypedExpressionType,
225+
ST: SqlType,
219226
Subselect<Self, ST>: Expression<SqlType = ST>,
220227
Self: SelectQuery<SqlType = ST>,
221228
{
@@ -228,7 +235,7 @@ where
228235

229236
impl<'a, ST, QS, DB, GB> AsInExpression<ST> for BoxedSelectStatement<'a, ST, QS, DB, GB>
230237
where
231-
ST: SqlType + TypedExpressionType,
238+
ST: SqlType,
232239
Subselect<BoxedSelectStatement<'a, ST, QS, DB, GB>, ST>: Expression<SqlType = ST>,
233240
{
234241
type InExpression = Subselect<Self, ST>;
@@ -241,7 +248,7 @@ where
241248
impl<ST, Combinator, Rule, Source, Rhs> AsInExpression<ST>
242249
for CombinationClause<Combinator, Rule, Source, Rhs>
243250
where
244-
ST: SqlType + TypedExpressionType,
251+
ST: SqlType,
245252
Self: SelectQuery<SqlType = ST>,
246253
Subselect<Self, ST>: Expression<SqlType = ST>,
247254
{
@@ -252,8 +259,8 @@ where
252259
}
253260
}
254261

255-
/// Query dsl node for an `IN (values)` clause containing
256-
/// a variable number of bind values.
262+
/// Query dsl node for the `values` part of an `IN (values)` clause
263+
/// containing a variable number of bind values.
257264
///
258265
/// Third party backend can customize the [`QueryFragment`]
259266
/// implementation of this query dsl node via
@@ -284,10 +291,18 @@ impl<ST, I> Expression for Many<ST, I>
284291
where
285292
ST: TypedExpressionType,
286293
{
287-
type SqlType = ST;
294+
// Comma-ed fake expressions are not usable directly in SQL
295+
// This is only implemented so that we can use the usual SelectableExpression & co traits
296+
// as constraints for the same implementations on [`In`] and [`NotIn`]
297+
type SqlType = NotSelectable;
288298
}
289299

290-
impl<ST, I> MaybeEmpty for Many<ST, I> {
300+
impl<ST, I> InExpression for Many<ST, I>
301+
where
302+
ST: SqlType,
303+
{
304+
type SqlType = ST;
305+
291306
fn is_empty(&self) -> bool {
292307
self.values.is_empty()
293308
}

diesel/src/expression/mod.rs

+12-15
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ pub mod expression_types {
139139
///
140140
/// If you see an error message containing `FromSqlRow` and this type
141141
/// recheck that you have written a valid select clause
142+
///
143+
/// These may notably be used as intermediate Expression nodes of the query builder
144+
/// which do not map to actual SQL expressions (for implementation simplicity).
142145
#[derive(Debug, Clone, Copy)]
143146
pub struct NotSelectable;
144147

@@ -1041,18 +1044,12 @@ impl<'a, QS, ST, DB, GB, IsAggregate> ValidGrouping<GB>
10411044
type IsAggregate = IsAggregate;
10421045
}
10431046

1044-
/// Converts a tuple of values into a tuple of Diesel expressions.
1045-
///
1046-
/// This trait is similar to [`AsExpression`], but it operates on tuples.
1047-
/// The expressions must all be of the same SQL type.
1048-
///
1049-
pub trait AsExpressionList<ST> {
1050-
/// The final output expression
1051-
type Expression;
1052-
1053-
/// Perform the conversion
1054-
// That's public API, we cannot change
1055-
// that to appease clippy
1056-
#[allow(clippy::wrong_self_convention)]
1057-
fn as_expression_list(self) -> Self::Expression;
1058-
}
1047+
// Some amount of backwards-compat
1048+
// We used to require `AsExpressionList` on the `array` function.
1049+
// Now we require `IntoArrayExpression` instead, which means something very different.
1050+
// However for most people just checking this bound to call `array`, this won't break.
1051+
// Only people who directly implement `AsExpressionList` would break, but I expect that to be
1052+
// nobody.
1053+
#[doc(hidden)]
1054+
#[cfg(feature = "postgres_backend")]
1055+
pub use crate::pg::expression::array::IntoArrayExpression as AsExpressionList;

diesel/src/expression/subselect.rs

+13-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,19 @@
11
use std::marker::PhantomData;
22

3-
use crate::expression::array_comparison::MaybeEmpty;
3+
use crate::expression::array_comparison::InExpression;
44
use crate::expression::*;
55
use crate::query_builder::*;
66
use crate::result::QueryResult;
77

8+
/// This struct tells our type system that the whatever we put in `values`
9+
/// will be handled by SQL as an expression of type `ST`.
10+
/// It also implements the usual `SelectableExpression` and `AppearsOnTable` traits
11+
/// (which is useful when using this as an expression). To enforce correctness here, it checks
12+
/// the dedicated [`ValidSubselect`]. This however does not check that the `SqlType` of
13+
/// [`SelectQuery`], matches `ST`, so appropriate constraints should be checked in places that
14+
/// construct Subselect. (It's not always equal, notably .single_value() makes `ST` nullable, and
15+
/// `exists` checks bounds on `SubSelect<T, Bool>` although there is actually no such subquery in
16+
/// the final SQL.)
817
#[derive(Debug, Copy, Clone, QueryId)]
918
pub struct Subselect<T, ST> {
1019
values: T,
@@ -24,10 +33,12 @@ impl<T: SelectQuery, ST> Expression for Subselect<T, ST>
2433
where
2534
ST: SqlType + TypedExpressionType,
2635
{
36+
// This is useful for `.single_value()`
2737
type SqlType = ST;
2838
}
2939

30-
impl<T, ST> MaybeEmpty for Subselect<T, ST> {
40+
impl<T, ST: SqlType> InExpression for Subselect<T, ST> {
41+
type SqlType = ST;
3142
fn is_empty(&self) -> bool {
3243
false
3344
}

0 commit comments

Comments
 (0)