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 left_sql = operand_sql(expr);
156    let mut values_iter = values.into_iter();
157
158    match values_iter.next() {
159        None => {
160            if negated {
161                left_sql.append(SQL::raw("NOT IN (SELECT NULL WHERE 1=0)"))
162            } else {
163                left_sql.append(SQL::raw("IN (SELECT NULL WHERE 1=0)"))
164            }
165        }
166        Some(first_value) => {
167            let mut result = left_sql;
168            if negated {
169                result = result.push(Token::NOT);
170            }
171
172            result = result
173                .push(Token::IN)
174                .push(Token::LPAREN)
175                .append(ComparisonOperand::into_comparison_sql(first_value));
176
177            for value in values_iter {
178                result = result
179                    .push(Token::COMMA)
180                    .append(ComparisonOperand::into_comparison_sql(value));
181            }
182            result.push(Token::RPAREN)
183        }
184    }
185}
186
187/// IN subquery check.
188///
189/// Returns true if the expression's value is in the subquery results.
190/// Accepts a single expression or a tuple of expressions as the LHS:
191///
192/// ```rust
193/// # let _ = r####"
194/// in_subquery(users.id, sub)                      // single column
195/// in_subquery((users.id, users.name), sub)        // multi-column
196/// # "####;
197/// ```
198pub fn in_subquery<'a, V, L, S, M>(
199    lhs: L,
200    subquery: S,
201) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, L::Aggregate>
202where
203    V: SQLParam + 'a,
204    L: InSubqueryLhs<'a, V, M>,
205    S: Expr<'a, V>,
206    L::SQLType: Compatible<S::SQLType>,
207{
208    SQLExpr::new(
209        lhs.into_lhs_sql()
210            .push(Token::IN)
211            .append(subquery.into_sql().parens()),
212    )
213}
214
215/// NOT IN subquery check.
216///
217/// Accepts a single expression or a tuple of expressions as the LHS:
218///
219/// ```rust
220/// # let _ = r####"
221/// not_in_subquery(users.id, sub)                  // single column
222/// not_in_subquery((users.id, users.name), sub)    // multi-column
223/// # "####;
224/// ```
225pub fn not_in_subquery<'a, V, L, S, M>(
226    lhs: L,
227    subquery: S,
228) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, L::Aggregate>
229where
230    V: SQLParam + 'a,
231    L: InSubqueryLhs<'a, V, M>,
232    S: Expr<'a, V>,
233    L::SQLType: Compatible<S::SQLType>,
234{
235    SQLExpr::new(
236        lhs.into_lhs_sql()
237            .push(Token::NOT)
238            .push(Token::IN)
239            .append(subquery.into_sql().parens()),
240    )
241}
242
243// =============================================================================
244// EXISTS
245// =============================================================================
246
247/// EXISTS subquery check.
248///
249/// Returns true if the subquery returns any rows.
250pub fn exists<'a, V, S>(
251    subquery: S,
252) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar>
253where
254    V: SQLParam + 'a,
255    S: ToSQL<'a, V>,
256{
257    SQLExpr::new(
258        SQL::from_iter([Token::EXISTS, Token::LPAREN])
259            .append(subquery.into_sql())
260            .push(Token::RPAREN),
261    )
262}
263
264/// NOT EXISTS subquery check.
265///
266/// Returns true if the subquery returns no rows.
267pub fn not_exists<'a, V, S>(
268    subquery: S,
269) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar>
270where
271    V: SQLParam + 'a,
272    S: ToSQL<'a, V>,
273{
274    SQLExpr::new(
275        SQL::from_iter([Token::NOT, Token::EXISTS, Token::LPAREN])
276            .append(subquery.into_sql())
277            .push(Token::RPAREN),
278    )
279}