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, 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)`
42impl<'a, V, E> InSubqueryLhs<'a, V, Single> for E
43where
44    V: SQLParam + 'a,
45    E: Expr<'a, V>,
46{
47    type SQLType = E::SQLType;
48    type Aggregate = E::Aggregate;
49    fn into_lhs_sql(self) -> SQL<'a, V> {
50        self.into_expr_sql()
51    }
52}
53
54/// Tuple impls: `in_subquery((users.id, users.name), sub)`
55macro_rules! impl_in_subquery_lhs_tuple {
56    ($($E:ident),+; $($idx:tt),+) => {
57        impl<'a, V, $($E),+> InSubqueryLhs<'a, V, Multi> for ($($E,)+)
58        where
59            V: SQLParam + 'a,
60            $($E: Expr<'a, V>,)+
61        {
62            type SQLType = ($($E::SQLType,)+);
63            type Aggregate = Scalar;
64            fn into_lhs_sql(self) -> SQL<'a, V> {
65                ToSQL::into_sql(self).parens()
66            }
67        }
68    };
69}
70
71with_col_sizes_8!(impl_in_subquery_lhs_tuple);
72
73#[cfg(any(
74    feature = "col16",
75    feature = "col32",
76    feature = "col64",
77    feature = "col128",
78    feature = "col200"
79))]
80with_col_sizes_16!(impl_in_subquery_lhs_tuple);
81
82#[cfg(any(
83    feature = "col32",
84    feature = "col64",
85    feature = "col128",
86    feature = "col200"
87))]
88with_col_sizes_32!(impl_in_subquery_lhs_tuple);
89
90#[cfg(any(feature = "col64", feature = "col128", feature = "col200"))]
91with_col_sizes_64!(impl_in_subquery_lhs_tuple);
92
93#[cfg(any(feature = "col128", feature = "col200"))]
94with_col_sizes_128!(impl_in_subquery_lhs_tuple);
95
96#[cfg(feature = "col200")]
97with_col_sizes_200!(impl_in_subquery_lhs_tuple);
98
99// =============================================================================
100// IN Array
101// =============================================================================
102
103/// IN array check.
104///
105/// Returns true if the expression's value is in the provided array.
106/// Requires the expression type to be compatible with the array element type.
107pub fn in_array<'a, V, E, I, R>(
108    expr: E,
109    values: I,
110) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
111where
112    V: SQLParam + 'a,
113    E: Expr<'a, V>,
114    I: IntoIterator<Item = R>,
115    R: Expr<'a, V>,
116    E::SQLType: Compatible<R::SQLType>,
117{
118    SQLExpr::new(in_array_impl(expr, values, false))
119}
120
121/// NOT IN array check.
122///
123/// Returns true if the expression's value is NOT in the provided array.
124/// Requires the expression type to be compatible with the array element type.
125pub fn not_in_array<'a, V, E, I, R>(
126    expr: E,
127    values: I,
128) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, E::Aggregate>
129where
130    V: SQLParam + 'a,
131    E: Expr<'a, V>,
132    I: IntoIterator<Item = R>,
133    R: Expr<'a, V>,
134    E::SQLType: Compatible<R::SQLType>,
135{
136    SQLExpr::new(in_array_impl(expr, values, true))
137}
138
139fn in_array_impl<'a, V, E, I, R>(expr: E, values: I, negated: bool) -> SQL<'a, V>
140where
141    V: SQLParam + 'a,
142    E: Expr<'a, V>,
143    I: IntoIterator<Item = R>,
144    R: Expr<'a, V>,
145    E::SQLType: Compatible<R::SQLType>,
146{
147    let left_sql = operand_sql(expr);
148    let mut values_iter = values.into_iter();
149
150    match values_iter.next() {
151        None => {
152            if negated {
153                left_sql.append(SQL::raw("NOT IN (SELECT NULL WHERE 1=0)"))
154            } else {
155                left_sql.append(SQL::raw("IN (SELECT NULL WHERE 1=0)"))
156            }
157        }
158        Some(first_value) => {
159            let mut result = left_sql;
160            if negated {
161                result = result.push(Token::NOT);
162            }
163
164            result = result
165                .push(Token::IN)
166                .push(Token::LPAREN)
167                .append(operand_sql(first_value));
168
169            for value in values_iter {
170                result = result.push(Token::COMMA).append(operand_sql(value));
171            }
172            result.push(Token::RPAREN)
173        }
174    }
175}
176
177/// IN subquery check.
178///
179/// Returns true if the expression's value is in the subquery results.
180/// Accepts a single expression or a tuple of expressions as the LHS:
181///
182/// ```rust
183/// # let _ = r####"
184/// in_subquery(users.id, sub)                      // single column
185/// in_subquery((users.id, users.name), sub)        // multi-column
186/// # "####;
187/// ```
188pub fn in_subquery<'a, V, L, S, M>(
189    lhs: L,
190    subquery: S,
191) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, L::Aggregate>
192where
193    V: SQLParam + 'a,
194    L: InSubqueryLhs<'a, V, M>,
195    S: Expr<'a, V>,
196    L::SQLType: Compatible<S::SQLType>,
197{
198    SQLExpr::new(
199        lhs.into_lhs_sql()
200            .push(Token::IN)
201            .append(subquery.into_sql().parens()),
202    )
203}
204
205/// NOT IN subquery check.
206///
207/// Accepts a single expression or a tuple of expressions as the LHS:
208///
209/// ```rust
210/// # let _ = r####"
211/// not_in_subquery(users.id, sub)                  // single column
212/// not_in_subquery((users.id, users.name), sub)    // multi-column
213/// # "####;
214/// ```
215pub fn not_in_subquery<'a, V, L, S, M>(
216    lhs: L,
217    subquery: S,
218) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, L::Aggregate>
219where
220    V: SQLParam + 'a,
221    L: InSubqueryLhs<'a, V, M>,
222    S: Expr<'a, V>,
223    L::SQLType: Compatible<S::SQLType>,
224{
225    SQLExpr::new(
226        lhs.into_lhs_sql()
227            .push(Token::NOT)
228            .push(Token::IN)
229            .append(subquery.into_sql().parens()),
230    )
231}
232
233// =============================================================================
234// EXISTS
235// =============================================================================
236
237/// EXISTS subquery check.
238///
239/// Returns true if the subquery returns any rows.
240pub fn exists<'a, V, S>(
241    subquery: S,
242) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar>
243where
244    V: SQLParam + 'a,
245    S: ToSQL<'a, V>,
246{
247    SQLExpr::new(
248        SQL::from_iter([Token::EXISTS, Token::LPAREN])
249            .append(subquery.into_sql())
250            .push(Token::RPAREN),
251    )
252}
253
254/// NOT EXISTS subquery check.
255///
256/// Returns true if the subquery returns no rows.
257pub fn not_exists<'a, V, S>(
258    subquery: S,
259) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar>
260where
261    V: SQLParam + 'a,
262    S: ToSQL<'a, V>,
263{
264    SQLExpr::new(
265        SQL::from_iter([Token::NOT, Token::EXISTS, Token::LPAREN])
266            .append(subquery.into_sql())
267            .push(Token::RPAREN),
268    )
269}