Skip to main content

drizzle_core/expr/
set.rs

1//! Set operations (IN, NOT IN, EXISTS, NOT EXISTS).
2
3use crate::dialect::DialectTypes;
4use crate::sql::{SQL, Token};
5use crate::traits::{SQLParam, ToSQL};
6use crate::types::{Compatible, DataType};
7
8use super::{AggregateKind, ComparisonOperand, Expr, NonNull, SQLExpr, Scalar};
9
10#[inline]
11fn operand_sql<'a, V, T>(value: T) -> SQL<'a, V>
12where
13    V: SQLParam + 'a,
14    T: Expr<'a, V>,
15{
16    value.into_expr_sql()
17}
18
19// =============================================================================
20// InSubqueryLhs — marker-parameterized trait for single exprs and tuples
21// =============================================================================
22
23/// Marker for a single `Expr` used as `IN (subquery)` LHS.
24#[doc(hidden)]
25pub enum Single {}
26
27/// Marker for a tuple of `Expr`s used as `IN (subquery)` LHS.
28#[doc(hidden)]
29pub enum Multi {}
30
31/// Left-hand side of an `IN (subquery)` expression.
32///
33/// Accepts single expressions (`col`) or tuples (`(col_a, col_b)`).
34/// The marker `M` is inferred — callers never specify it.
35pub trait InSubqueryLhs<'a, V: SQLParam, M>: Sized {
36    type SQLType: DataType;
37    type Aggregate: AggregateKind;
38    fn into_lhs_sql(self) -> SQL<'a, V>;
39}
40
41/// Single expression: `in_subquery(users.id, sub)`
42///
43/// The self-compatibility bound is what keeps condition tuples out of this
44/// impl. A tuple of conditions is an expression too, so without it a tuple of
45/// boolean-typed columns would match both this impl and the row-value impl
46/// below and the marker `M` could not be inferred. Every SQL type that names a
47/// column is compatible with itself; `Conjunction`, the SQL type of a condition
48/// list, deliberately is not.
49impl<'a, V, E> InSubqueryLhs<'a, V, Single> for E
50where
51    V: SQLParam + 'a,
52    E: Expr<'a, V>,
53    E::SQLType: Compatible<E::SQLType>,
54{
55    type SQLType = E::SQLType;
56    type Aggregate = E::Aggregate;
57    fn into_lhs_sql(self) -> SQL<'a, V> {
58        self.into_expr_sql()
59    }
60}
61
62/// Tuple impls: `in_subquery((users.id, users.name), sub)`
63macro_rules! impl_in_subquery_lhs_tuple {
64    ($($E:ident),+; $($idx:tt),+) => {
65        impl<'a, V, $($E),+> InSubqueryLhs<'a, V, Multi> for ($($E,)+)
66        where
67            V: SQLParam + 'a,
68            $($E: Expr<'a, V>,)+
69        {
70            type SQLType = ($($E::SQLType,)+);
71            type Aggregate = Scalar;
72            fn into_lhs_sql(self) -> SQL<'a, V> {
73                ToSQL::into_sql(self).parens()
74            }
75        }
76    };
77}
78
79with_col_sizes_8!(impl_in_subquery_lhs_tuple);
80
81#[cfg(any(
82    feature = "col16",
83    feature = "col32",
84    feature = "col64",
85    feature = "col128",
86    feature = "col200"
87))]
88with_col_sizes_16!(impl_in_subquery_lhs_tuple);
89
90#[cfg(any(
91    feature = "col32",
92    feature = "col64",
93    feature = "col128",
94    feature = "col200"
95))]
96with_col_sizes_32!(impl_in_subquery_lhs_tuple);
97
98#[cfg(any(feature = "col64", feature = "col128", feature = "col200"))]
99with_col_sizes_64!(impl_in_subquery_lhs_tuple);
100
101#[cfg(any(feature = "col128", feature = "col200"))]
102with_col_sizes_128!(impl_in_subquery_lhs_tuple);
103
104#[cfg(feature = "col200")]
105with_col_sizes_200!(impl_in_subquery_lhs_tuple);
106
107// =============================================================================
108// IN Array
109// =============================================================================
110
111/// IN array check.
112///
113/// Returns true if the expression's value is in the provided array.
114/// Requires the expression type to be compatible with the array element type.
115pub fn in_array<'a, V, E, I, R>(
116    expr: E,
117    values: I,
118) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
119where
120    V: SQLParam + 'a,
121    E: Expr<'a, V>,
122    I: IntoIterator<Item = R>,
123    R: ComparisonOperand<'a, V, E>,
124    E::SQLType: Compatible<<R as ComparisonOperand<'a, V, E>>::SQLType>,
125{
126    SQLExpr::new(in_array_impl(expr, values, false))
127}
128
129/// NOT IN array check.
130///
131/// Returns true if the expression's value is NOT in the provided array.
132/// Requires the expression type to be compatible with the array element type.
133pub fn not_in_array<'a, V, E, I, R>(
134    expr: E,
135    values: I,
136) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
137where
138    V: SQLParam + 'a,
139    E: Expr<'a, V>,
140    I: IntoIterator<Item = R>,
141    R: ComparisonOperand<'a, V, E>,
142    E::SQLType: Compatible<<R as ComparisonOperand<'a, V, E>>::SQLType>,
143{
144    SQLExpr::new(in_array_impl(expr, values, true))
145}
146
147fn in_array_impl<'a, V, E, I, R>(expr: E, values: I, negated: bool) -> SQL<'a, V>
148where
149    V: SQLParam + 'a,
150    E: Expr<'a, V>,
151    I: IntoIterator<Item = R>,
152    R: ComparisonOperand<'a, V, E>,
153    E::SQLType: Compatible<<R as ComparisonOperand<'a, V, E>>::SQLType>,
154{
155    let mut values_iter = values.into_iter();
156
157    match values_iter.next() {
158        None => SQL::raw(if negated { "TRUE" } else { "FALSE" }),
159        Some(first_value) => {
160            let mut result = operand_sql(expr);
161            if negated {
162                result = result.push(Token::NOT);
163            }
164
165            result = result
166                .push(Token::IN)
167                .push(Token::LPAREN)
168                .append(ComparisonOperand::into_comparison_sql(first_value));
169
170            for value in values_iter {
171                result = result
172                    .push(Token::COMMA)
173                    .append(ComparisonOperand::into_comparison_sql(value));
174            }
175            result.push(Token::RPAREN)
176        }
177    }
178}
179
180/// IN subquery check.
181///
182/// Returns true if the expression's value is in the subquery results.
183/// Accepts a single expression or a tuple of expressions as the LHS:
184///
185/// ```rust
186/// # let _ = r####"
187/// in_subquery(users.id, sub)                      // single column
188/// in_subquery((users.id, users.name), sub)        // multi-column
189/// # "####;
190/// ```
191pub fn in_subquery<'a, V, L, S, M>(
192    lhs: L,
193    subquery: S,
194) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, L::Aggregate>
195where
196    V: SQLParam + 'a,
197    L: InSubqueryLhs<'a, V, M>,
198    S: Expr<'a, V>,
199    L::SQLType: Compatible<S::SQLType>,
200{
201    SQLExpr::new(
202        lhs.into_lhs_sql()
203            .push(Token::IN)
204            .append(subquery.into_sql().parens()),
205    )
206}
207
208/// NOT IN subquery check.
209///
210/// Accepts a single expression or a tuple of expressions as the LHS:
211///
212/// ```rust
213/// # let _ = r####"
214/// not_in_subquery(users.id, sub)                  // single column
215/// not_in_subquery((users.id, users.name), sub)    // multi-column
216/// # "####;
217/// ```
218pub fn not_in_subquery<'a, V, L, S, M>(
219    lhs: L,
220    subquery: S,
221) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, L::Aggregate>
222where
223    V: SQLParam + 'a,
224    L: InSubqueryLhs<'a, V, M>,
225    S: Expr<'a, V>,
226    L::SQLType: Compatible<S::SQLType>,
227{
228    SQLExpr::new(
229        lhs.into_lhs_sql()
230            .push(Token::NOT)
231            .push(Token::IN)
232            .append(subquery.into_sql().parens()),
233    )
234}
235
236// =============================================================================
237// EXISTS
238// =============================================================================
239
240/// EXISTS subquery check.
241///
242/// Returns true if the subquery returns any rows.
243pub fn exists<'a, V, S>(
244    subquery: S,
245) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar>
246where
247    V: SQLParam + 'a,
248    S: ToSQL<'a, V>,
249{
250    SQLExpr::new(
251        SQL::from_iter([Token::EXISTS, Token::LPAREN])
252            .append(subquery.into_sql())
253            .push(Token::RPAREN),
254    )
255}
256
257/// NOT EXISTS subquery check.
258///
259/// Returns true if the subquery returns no rows.
260pub fn not_exists<'a, V, S>(
261    subquery: S,
262) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar>
263where
264    V: SQLParam + 'a,
265    S: ToSQL<'a, V>,
266{
267    SQLExpr::new(
268        SQL::from_iter([Token::NOT, Token::EXISTS, Token::LPAREN])
269            .append(subquery.into_sql())
270            .push(Token::RPAREN),
271    )
272}