drizzle_core/expr/
set.rs

1//! Set operations (IN, NOT IN, EXISTS, NOT EXISTS).
2
3use crate::sql::{SQL, Token};
4use crate::traits::{SQLParam, ToSQL};
5use crate::types::{Bool, Compatible};
6
7use super::{Expr, NonNull, SQLExpr, Scalar};
8
9// =============================================================================
10// IN Array
11// =============================================================================
12
13/// IN array check.
14///
15/// Returns true if the expression's value is in the provided array.
16/// Requires the expression type to be compatible with the array element type.
17pub fn in_array<'a, V, E, I, R>(expr: E, values: I) -> SQLExpr<'a, V, Bool, NonNull, Scalar>
18where
19    V: SQLParam + 'a,
20    E: Expr<'a, V>,
21    I: IntoIterator<Item = R>,
22    R: Expr<'a, V>,
23    E::SQLType: Compatible<R::SQLType>,
24{
25    let left_sql = expr.to_sql();
26    let mut values_iter = values.into_iter();
27
28    let sql = match values_iter.next() {
29        // Empty array: use FALSE condition since IN (NULL) behaves inconsistently
30        None => left_sql.append(SQL::raw("IN (SELECT NULL WHERE 1=0)")),
31        Some(first_value) => {
32            let mut result = left_sql
33                .push(Token::IN)
34                .push(Token::LPAREN)
35                .append(first_value.to_sql());
36            for value in values_iter {
37                result = result.push(Token::COMMA).append(value.to_sql());
38            }
39            result.push(Token::RPAREN)
40        }
41    };
42    SQLExpr::new(sql)
43}
44
45/// NOT IN array check.
46///
47/// Returns true if the expression's value is NOT in the provided array.
48/// Requires the expression type to be compatible with the array element type.
49pub fn not_in_array<'a, V, E, I, R>(expr: E, values: I) -> SQLExpr<'a, V, Bool, NonNull, Scalar>
50where
51    V: SQLParam + 'a,
52    E: Expr<'a, V>,
53    I: IntoIterator<Item = R>,
54    R: Expr<'a, V>,
55    E::SQLType: Compatible<R::SQLType>,
56{
57    let left_sql = expr.to_sql();
58    let mut values_iter = values.into_iter();
59
60    let sql = match values_iter.next() {
61        // Empty array: use TRUE condition since NOT IN (empty) is always true
62        // We use the same pattern as in_array for consistency
63        None => left_sql.append(SQL::raw("NOT IN (SELECT NULL WHERE 1=0)")),
64        Some(first_value) => {
65            let mut result = left_sql
66                .push(Token::NOT)
67                .push(Token::IN)
68                .push(Token::LPAREN)
69                .append(first_value.to_sql());
70            for value in values_iter {
71                result = result.push(Token::COMMA).append(value.to_sql());
72            }
73            result.push(Token::RPAREN)
74        }
75    };
76    SQLExpr::new(sql)
77}
78
79/// IN subquery check.
80///
81/// Returns true if the expression's value is in the subquery results.
82pub fn in_subquery<'a, V, E, S>(expr: E, subquery: S) -> SQLExpr<'a, V, Bool, NonNull, Scalar>
83where
84    V: SQLParam + 'a,
85    E: ToSQL<'a, V>,
86    S: ToSQL<'a, V>,
87{
88    SQLExpr::new(
89        expr.to_sql()
90            .push(Token::IN)
91            .append(subquery.to_sql().parens()),
92    )
93}
94
95/// NOT IN subquery check.
96pub fn not_in_subquery<'a, V, E, S>(expr: E, subquery: S) -> SQLExpr<'a, V, Bool, NonNull, Scalar>
97where
98    V: SQLParam + 'a,
99    E: ToSQL<'a, V>,
100    S: ToSQL<'a, V>,
101{
102    SQLExpr::new(
103        expr.to_sql()
104            .push(Token::NOT)
105            .push(Token::IN)
106            .append(subquery.to_sql().parens()),
107    )
108}
109
110// =============================================================================
111// EXISTS
112// =============================================================================
113
114/// EXISTS subquery check.
115///
116/// Returns true if the subquery returns any rows.
117pub fn exists<'a, V, S>(subquery: S) -> SQLExpr<'a, V, Bool, NonNull, Scalar>
118where
119    V: SQLParam + 'a,
120    S: ToSQL<'a, V>,
121{
122    SQLExpr::new(
123        SQL::from_iter([Token::EXISTS, Token::LPAREN])
124            .append(subquery.to_sql())
125            .push(Token::RPAREN),
126    )
127}
128
129/// NOT EXISTS subquery check.
130///
131/// Returns true if the subquery returns no rows.
132pub fn not_exists<'a, V, S>(subquery: S) -> SQLExpr<'a, V, Bool, NonNull, Scalar>
133where
134    V: SQLParam + 'a,
135    S: ToSQL<'a, V>,
136{
137    SQLExpr::new(
138        SQL::from_iter([Token::NOT, Token::EXISTS, Token::LPAREN])
139            .append(subquery.to_sql())
140            .push(Token::RPAREN),
141    )
142}