drizzle_core/expr/set.rs
1//! Membership tests: `IN`, `NOT IN`, `EXISTS` and `NOT EXISTS`.
2//!
3//! Each returns the dialect's boolean. `IN` and `NOT IN` are NULL when the
4//! left side or a compared value is NULL (tracked through their sources, as
5//! for [comparisons](super::eq)); `EXISTS` is never NULL. Values compared with
6//! `IN` must have a SQL type compatible with the left-hand side.
7
8use crate::dialect::DialectTypes;
9use crate::sql::{SQL, Token};
10use crate::traits::{SQLParam, ToSQL};
11use crate::types::{Compatible, DataType};
12
13use super::{AggregateKind, ComparisonOperand, Expr, ExprSources, NonNull, SQLExpr, Scalar};
14use crate::scope::{Arg, ScopeOnly};
15
16/// Sources of `expr IN (values)`: NULL when the operand or a value is.
17type InArraySources<'a, V, E, R> = (
18 Arg<<E as Expr<'a, V>>::Nullable, <E as ExprSources>::Sources>,
19 Arg<<R as ComparisonOperand<'a, V, E>>::Nullable, <R as ComparisonOperand<'a, V, E>>::Sources>,
20);
21
22/// Sources of `lhs IN (subquery)`: NULL when the operand or a subquery value is.
23type InSubquerySources<'a, V, L, S, M> = (
24 <L as InSubqueryLhs<'a, V, M>>::Sources,
25 Arg<<S as Expr<'a, V>>::Nullable, <S as ExprSources>::Sources>,
26);
27
28#[inline]
29fn operand_sql<'a, V, T>(value: T) -> SQL<'a, V>
30where
31 V: SQLParam + 'a,
32 T: Expr<'a, V>,
33{
34 value.into_expr_sql()
35}
36
37// =============================================================================
38// InSubqueryLhs — marker-parameterized trait for single exprs and tuples
39// =============================================================================
40
41/// Marker: the left side of `IN (subquery)` is one expression.
42#[doc(hidden)]
43pub enum Single {}
44
45/// Marker: the left side of `IN (subquery)` is a tuple (a row value).
46#[doc(hidden)]
47pub enum Multi {}
48
49/// Left side of [`in_subquery`] and [`not_in_subquery`].
50///
51/// Implemented for single expressions (`users.id`) and for tuples of
52/// expressions (`(users.id, users.name)`), which render as a row value. The
53/// marker `M` is inferred; callers never name it.
54pub trait InSubqueryLhs<'a, V: SQLParam, M>: Sized {
55 /// SQL type of the left side (a tuple of types for a row value).
56 type SQLType: DataType;
57 /// Aggregate kind of the left side.
58 type Aggregate: AggregateKind;
59 /// Operand sources, with each element's declared nullability.
60 type Sources;
61 /// Renders the left side.
62 fn into_lhs_sql(self) -> SQL<'a, V>;
63}
64
65/// A single expression: `in_subquery(users.id, sub)`.
66///
67/// The self-compatibility bound is what keeps condition tuples out of this
68/// impl. A tuple of conditions is an expression too, so without it a tuple of
69/// boolean-typed columns would match both this impl and the row-value impl
70/// below and the marker `M` could not be inferred. Every SQL type that names a
71/// column is compatible with itself; `Conjunction`, the SQL type of a condition
72/// list, deliberately is not.
73impl<'a, V, E> InSubqueryLhs<'a, V, Single> for E
74where
75 V: SQLParam + 'a,
76 E: Expr<'a, V>,
77 E::SQLType: Compatible<E::SQLType>,
78{
79 type SQLType = E::SQLType;
80 type Aggregate = E::Aggregate;
81 type Sources = Arg<E::Nullable, E::Sources>;
82 fn into_lhs_sql(self) -> SQL<'a, V> {
83 self.into_expr_sql()
84 }
85}
86
87// Tuples: `in_subquery((users.id, users.name), sub)`.
88macro_rules! impl_in_subquery_lhs_tuple {
89 ($($E:ident),+; $($idx:tt),+) => {
90 impl<'a, V, $($E),+> InSubqueryLhs<'a, V, Multi> for ($($E,)+)
91 where
92 V: SQLParam + 'a,
93 $($E: Expr<'a, V>,)+
94 {
95 type SQLType = ($($E::SQLType,)+);
96 type Aggregate = Scalar;
97 type Sources = impl_in_subquery_lhs_tuple!(@sources $($E),+);
98 fn into_lhs_sql(self) -> SQL<'a, V> {
99 ToSQL::into_sql(self).parens()
100 }
101 }
102 };
103 (@sources $E:ident) => {
104 Arg<<$E as Expr<'a, V>>::Nullable, <$E as ExprSources>::Sources>
105 };
106 (@sources $E:ident, $($rest:ident),+) => {
107 (
108 Arg<<$E as Expr<'a, V>>::Nullable, <$E as ExprSources>::Sources>,
109 impl_in_subquery_lhs_tuple!(@sources $($rest),+),
110 )
111 };
112}
113
114with_col_sizes_8!(impl_in_subquery_lhs_tuple);
115
116#[cfg(any(
117 feature = "col16",
118 feature = "col32",
119 feature = "col64",
120 feature = "col128",
121 feature = "col200"
122))]
123with_col_sizes_16!(impl_in_subquery_lhs_tuple);
124
125#[cfg(any(
126 feature = "col32",
127 feature = "col64",
128 feature = "col128",
129 feature = "col200"
130))]
131with_col_sizes_32!(impl_in_subquery_lhs_tuple);
132
133#[cfg(any(feature = "col64", feature = "col128", feature = "col200"))]
134with_col_sizes_64!(impl_in_subquery_lhs_tuple);
135
136#[cfg(any(feature = "col128", feature = "col200"))]
137with_col_sizes_128!(impl_in_subquery_lhs_tuple);
138
139#[cfg(feature = "col200")]
140with_col_sizes_200!(impl_in_subquery_lhs_tuple);
141
142// =============================================================================
143// IN Array
144// =============================================================================
145
146/// Membership in a list of values (`expr IN (v1, v2, ...)`).
147///
148/// `values` is any iterator, such as an array or `Vec`; each value must have
149/// a SQL type compatible with `expr`. An empty list renders `FALSE`, since
150/// nothing is in an empty list. The result is the dialect's boolean, NULL
151/// when `expr` or a value is.
152///
153/// # Examples
154///
155/// ```rust
156/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
157/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
158/// # #[derive(Clone, Debug)] struct Value(String);
159/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
160/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
161/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
162/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
163/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
164/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
165/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
166/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
167/// let cond = in_array(users.name, ["alice", "bob"]);
168/// assert_eq!(cond.sql(), r#""users"."name" IN (?, ?)"#);
169///
170/// let none = in_array(users.name, Vec::<&str>::new());
171/// assert_eq!(none.sql(), "FALSE");
172/// ```
173///
174/// # Type safety
175///
176/// Values of an incompatible type do not compile:
177///
178/// ```rust,compile_fail
179/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
180/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
181/// # #[derive(Clone, Debug)] struct Value(String);
182/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
183/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
184/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
185/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
186/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
187/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
188/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
189/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
190/// let wrong = in_array(users.id, ["a", "b"]);
191/// ```
192#[allow(clippy::type_complexity)]
193pub fn in_array<'a, V, E, I, R>(
194 expr: E,
195 values: I,
196) -> SQLExpr<
197 'a,
198 V,
199 <V::DialectMarker as DialectTypes>::Bool,
200 NonNull,
201 E::Aggregate,
202 InArraySources<'a, V, E, R>,
203>
204where
205 V: SQLParam + 'a,
206 E: Expr<'a, V>,
207 I: IntoIterator<Item = R>,
208 R: ComparisonOperand<'a, V, E>,
209 E::SQLType: Compatible<<R as ComparisonOperand<'a, V, E>>::SQLType>,
210{
211 SQLExpr::new(in_array_impl(expr, values, false))
212}
213
214/// Non-membership in a list of values (`expr NOT IN (v1, v2, ...)`).
215///
216/// Same rules as [`in_array`]. An empty list renders `TRUE`.
217///
218/// # Examples
219///
220/// ```rust
221/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
222/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
223/// # #[derive(Clone, Debug)] struct Value(String);
224/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
225/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
226/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
227/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
228/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
229/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
230/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
231/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
232/// let cond = not_in_array(users.age, [0, 1, 2]);
233/// assert_eq!(cond.sql(), r#""users"."age" NOT IN (?, ?, ?)"#);
234/// ```
235#[allow(clippy::type_complexity)]
236pub fn not_in_array<'a, V, E, I, R>(
237 expr: E,
238 values: I,
239) -> SQLExpr<
240 'a,
241 V,
242 <V::DialectMarker as DialectTypes>::Bool,
243 NonNull,
244 E::Aggregate,
245 InArraySources<'a, V, E, R>,
246>
247where
248 V: SQLParam + 'a,
249 E: Expr<'a, V>,
250 I: IntoIterator<Item = R>,
251 R: ComparisonOperand<'a, V, E>,
252 E::SQLType: Compatible<<R as ComparisonOperand<'a, V, E>>::SQLType>,
253{
254 SQLExpr::new(in_array_impl(expr, values, true))
255}
256
257fn in_array_impl<'a, V, E, I, R>(expr: E, values: I, negated: bool) -> SQL<'a, V>
258where
259 V: SQLParam + 'a,
260 E: Expr<'a, V>,
261 I: IntoIterator<Item = R>,
262 R: ComparisonOperand<'a, V, E>,
263 E::SQLType: Compatible<<R as ComparisonOperand<'a, V, E>>::SQLType>,
264{
265 let mut values_iter = values.into_iter();
266
267 match values_iter.next() {
268 None => SQL::raw(if negated { "TRUE" } else { "FALSE" }),
269 Some(first_value) => {
270 let mut result = operand_sql(expr);
271 if negated {
272 result = result.push(Token::NOT);
273 }
274
275 result = result
276 .push(Token::IN)
277 .push(Token::LPAREN)
278 .append(ComparisonOperand::into_comparison_sql(first_value));
279
280 for value in values_iter {
281 result = result
282 .push(Token::COMMA)
283 .append(ComparisonOperand::into_comparison_sql(value));
284 }
285 result.push(Token::RPAREN)
286 }
287 }
288}
289
290/// Membership in a subquery's rows (`lhs IN (SELECT ...)`).
291///
292/// `lhs` is one expression or a tuple of expressions (a row value, such as
293/// `(users.id, users.name)`). The subquery's SQL type must be compatible
294/// with `lhs`. Pass a select query built with the dialect's query builder;
295/// its single column (or tuple of columns) gives the subquery's type. The
296/// result is the dialect's boolean, NULL when `lhs` or a subquery value is.
297///
298/// # Examples
299///
300/// ```rust
301/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
302/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
303/// # #[derive(Clone, Debug)] struct Value(String);
304/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
305/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
306/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
307/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
308/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
309/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
310/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
311/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
312/// # let banned_ids: SQLExpr<'_, Value, Int> = SQLExpr::new(SQL::raw("SELECT user_id FROM bans"));
313/// // `banned_ids` is a one-column subquery of integers.
314/// let cond = in_subquery(users.id, banned_ids);
315/// assert_eq!(cond.sql(), r#""users"."id" IN (SELECT user_id FROM bans)"#);
316/// ```
317#[allow(clippy::type_complexity)]
318pub fn in_subquery<'a, V, L, S, M>(
319 lhs: L,
320 subquery: S,
321) -> SQLExpr<
322 'a,
323 V,
324 <V::DialectMarker as DialectTypes>::Bool,
325 NonNull,
326 L::Aggregate,
327 InSubquerySources<'a, V, L, S, M>,
328>
329where
330 V: SQLParam + 'a,
331 L: InSubqueryLhs<'a, V, M>,
332 S: Expr<'a, V>,
333 L::SQLType: Compatible<S::SQLType>,
334{
335 SQLExpr::new(
336 lhs.into_lhs_sql()
337 .push(Token::IN)
338 .append(subquery.into_sql().parens()),
339 )
340}
341
342/// Non-membership in a subquery's rows (`lhs NOT IN (SELECT ...)`).
343///
344/// Same rules as [`in_subquery`]. Note that SQL's `NOT IN` gives no rows
345/// when the subquery returns any NULL.
346///
347/// # Examples
348///
349/// ```rust
350/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
351/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
352/// # #[derive(Clone, Debug)] struct Value(String);
353/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
354/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
355/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
356/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
357/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
358/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
359/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
360/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
361/// # let banned_ids: SQLExpr<'_, Value, Int> = SQLExpr::new(SQL::raw("SELECT user_id FROM bans"));
362/// let cond = not_in_subquery(users.id, banned_ids);
363/// assert_eq!(cond.sql(), r#""users"."id" NOT IN (SELECT user_id FROM bans)"#);
364/// ```
365#[allow(clippy::type_complexity)]
366pub fn not_in_subquery<'a, V, L, S, M>(
367 lhs: L,
368 subquery: S,
369) -> SQLExpr<
370 'a,
371 V,
372 <V::DialectMarker as DialectTypes>::Bool,
373 NonNull,
374 L::Aggregate,
375 InSubquerySources<'a, V, L, S, M>,
376>
377where
378 V: SQLParam + 'a,
379 L: InSubqueryLhs<'a, V, M>,
380 S: Expr<'a, V>,
381 L::SQLType: Compatible<S::SQLType>,
382{
383 SQLExpr::new(
384 lhs.into_lhs_sql()
385 .push(Token::NOT)
386 .push(Token::IN)
387 .append(subquery.into_sql().parens()),
388 )
389}
390
391// =============================================================================
392// EXISTS
393// =============================================================================
394
395/// A complete `SELECT` statement, accepted by [`exists`] and [`not_exists`].
396///
397/// The dialect select builders implement it once they hold a finished
398/// `SELECT`. `INSERT`, `UPDATE` and `DELETE` builders never do, even with
399/// `RETURNING`. Raw [`SQL`] is also accepted.
400#[diagnostic::on_unimplemented(
401 message = "`{Self}` is not a SELECT query",
402 label = "EXISTS takes a subquery built with `select(...).from(...)`"
403)]
404pub trait SelectQuery {}
405
406impl<V: SQLParam> SelectQuery for SQL<'_, V> {}
407impl<T: SelectQuery + ?Sized> SelectQuery for &T {}
408
409/// Whether a subquery returns any row (`EXISTS (SELECT ...)`).
410///
411/// `subquery` must be a `SELECT` (see [`SelectQuery`]). The result is the
412/// dialect's boolean, never NULL, and scalar.
413///
414/// # Examples
415///
416/// ```rust
417/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
418/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
419/// # #[derive(Clone, Debug)] struct Value(String);
420/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
421/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
422/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
423/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
424/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
425/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
426/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
427/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
428/// let has_posts = exists::<Value, _>(SQL::raw("SELECT 1 FROM posts"));
429/// assert_eq!(has_posts.sql(), "EXISTS (SELECT 1 FROM posts)");
430/// ```
431pub fn exists<'a, V, S>(
432 subquery: S,
433) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar, ScopeOnly<S::Sources>>
434where
435 V: SQLParam + 'a,
436 S: ToSQL<'a, V> + ExprSources + SelectQuery,
437{
438 SQLExpr::new(
439 SQL::from_iter([Token::EXISTS, Token::LPAREN])
440 .append(subquery.into_sql())
441 .push(Token::RPAREN),
442 )
443}
444
445/// Whether a subquery returns no rows (`NOT EXISTS (SELECT ...)`).
446///
447/// Same rules as [`exists`].
448///
449/// # Examples
450///
451/// ```rust
452/// # use drizzle_core::dialect::{Dialect, DialectTypes, SQLiteDialect as D};
453/// # use drizzle_core::{ColumnRef, SQL, SQLParam, expr::*};
454/// # #[derive(Clone, Debug)] struct Value(String);
455/// # impl SQLParam for Value { const DIALECT: Dialect = Dialect::SQLite; type DialectMarker = D; }
456/// # impl<X: ToString> From<X> for Value { fn from(v: X) -> Self { Value(v.to_string()) } }
457/// # impl From<Value> for std::borrow::Cow<'_, Value> { fn from(v: Value) -> Self { Self::Owned(v) } }
458/// # type C<X, N = NonNull> = &'static SQLExpr<'static, Value, X, N>;
459/// # fn col<X: drizzle_core::types::DataType, N: Nullability>(c: &'static str) -> C<X, N> { Box::leak(Box::new(SQLExpr::new(SQL::column(ColumnRef::sql("users", c))))) }
460/// # type Int = <D as DialectTypes>::Int; type Text = <D as DialectTypes>::Text; type Real = <D as DialectTypes>::Double;
461/// # struct Users { id: C<Int>, age: C<Int>, name: C<Text>, email: C<Text, Null>, score: C<Real, Null>, active: C<<D as DialectTypes>::Bool>, created_at: C<<D as DialectTypes>::Timestamp> }
462/// # let users = Users { id: col("id"), age: col("age"), name: col("name"), email: col("email"), score: col("score"), active: col("active"), created_at: col("created_at") };
463/// let no_posts = not_exists::<Value, _>(SQL::raw("SELECT 1 FROM posts"));
464/// assert_eq!(no_posts.sql(), "NOT EXISTS (SELECT 1 FROM posts)");
465/// ```
466pub fn not_exists<'a, V, S>(
467 subquery: S,
468) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, NonNull, Scalar, ScopeOnly<S::Sources>>
469where
470 V: SQLParam + 'a,
471 S: ToSQL<'a, V> + ExprSources + SelectQuery,
472{
473 SQLExpr::new(
474 SQL::from_iter([Token::NOT, Token::EXISTS, Token::LPAREN])
475 .append(subquery.into_sql())
476 .push(Token::RPAREN),
477 )
478}