drizzle_core/expr/cond.rs
1//! Condition lists: tuples as conjunctions, plus the [`all`] and [`any`] combinators.
2//!
3//! A tuple of conditions *is* a condition. It renders as the parenthesized AND
4//! of its elements and carries exactly the guarantees a chain of
5//! [`and`](super::and) calls would, so `and(a, and(b, c))` can be written
6//! `(a, b, c)` anywhere a condition is accepted:
7//!
8//! ```rust
9//! # let _ = r####"
10//! use drizzle_core::expr::{all, any, eq, gt};
11//!
12//! // WHERE ("users"."active" = TRUE AND "users"."age" > 18 AND "users"."role" = 'admin')
13//! query.r#where((eq(users.active, true), gt(users.age, 18), eq(users.role, "admin")));
14//!
15//! // Flat OR lists too
16//! query.r#where(any((eq(users.role, "admin"), eq(users.role, "moderator"))));
17//!
18//! // Tuples nest inside or(), and inside each other
19//! query.r#where(or((a, b), (c, d)));
20//! # "####;
21//! ```
22//!
23//! # Optional elements
24//!
25//! Any element may be an [`Option`]. `None` contributes nothing to the rendered
26//! SQL, which makes dynamic filters composable without building the condition
27//! by hand:
28//!
29//! ```rust
30//! # let _ = r####"
31//! let name_filter = name.map(|n| eq(users.name, n));
32//! query.r#where((gt(users.age, 18), name_filter));
33//! // Some("bob") => ("users"."age" > ? AND "users"."name" = ?)
34//! // None => ("users"."age" > ?)
35//! # "####;
36//! ```
37//!
38//! When *every* element is `None` the list has nothing to combine, so it
39//! renders as the identity of its operator: `TRUE` for a conjunction (a tuple
40//! or [`all`]) and `FALSE` for a disjunction ([`any`]). A conjunction that
41//! filters nothing therefore behaves like an absent `WHERE` clause, while an
42//! empty disjunction fails closed rather than silently matching every row.
43
44use crate::dialect::DialectTypes;
45use crate::sql::{SQL, Token};
46use crate::traits::SQLParam;
47use crate::types::BooleanLike;
48
49use super::{AggOr, AggregateKind, Expr, NullOr, Nullability, SQLExpr};
50
51/// Rendered SQL for a conjunction whose elements are all absent.
52const EMPTY_CONJUNCTION: &str = "TRUE";
53
54/// Rendered SQL for a disjunction whose elements are all absent.
55const EMPTY_DISJUNCTION: &str = "FALSE";
56
57mod sealed {
58 pub trait Sealed {}
59}
60
61// =============================================================================
62// ConditionSink
63// =============================================================================
64
65/// Accumulator threaded through a [`ConditionList`] while it renders.
66///
67/// Only [`ConditionList`] implementations interact with this type, and the
68/// trait is sealed, so it is an implementation detail of the crate.
69#[doc(hidden)]
70#[derive(Debug)]
71pub struct ConditionSink<'a, V: SQLParam> {
72 sql: SQL<'a, V>,
73 separator: Token,
74 len: usize,
75}
76
77impl<'a, V: SQLParam + 'a> ConditionSink<'a, V> {
78 fn new(separator: Token) -> Self {
79 Self {
80 sql: SQL::empty(),
81 separator,
82 len: 0,
83 }
84 }
85
86 /// Append one rendered condition. `None` contributes nothing.
87 pub fn push(&mut self, condition: Option<SQL<'a, V>>) {
88 let Some(condition) = condition else { return };
89 if self.len > 0 {
90 self.sql.push_mut(self.separator);
91 }
92 self.sql.append_mut(condition);
93 self.len += 1;
94 }
95
96 fn finish(self, empty: &'static str) -> SQL<'a, V> {
97 if self.len == 0 {
98 SQL::raw(empty)
99 } else {
100 self.sql.parens()
101 }
102 }
103}
104
105// =============================================================================
106// ConditionList
107// =============================================================================
108
109/// A list of SQL conditions combined under a single logical operator.
110///
111/// Implemented for tuples whose every element is a boolean expression — or an
112/// [`Option`] of one — for arities 1..=8, extended to 16 by the `col16`
113/// feature. The associated markers fold across the elements
114/// exactly as chained [`and`](super::and) calls would: the list is nullable if
115/// any element is nullable, and aggregate if any element is aggregate.
116///
117/// A bare tuple additionally *is* a condition (it implements
118/// [`Expr`](super::Expr)) up to arity 8. Past that, combine through [`all`] or
119/// [`any`], or nest tuples — the result is the same flat AND.
120///
121/// This trait is sealed; the crate provides every implementation.
122#[diagnostic::on_unimplemented(
123 message = "`{Self}` is not a list of SQL conditions",
124 label = "expected a tuple of boolean expressions",
125 note = "every element must be a boolean-typed expression, or an `Option` of one"
126)]
127pub trait ConditionList<'a, V: SQLParam>: sealed::Sealed {
128 /// Nullability folded across every element.
129 type Nullable: Nullability;
130
131 /// Aggregate kind folded across every element.
132 type Aggregate: AggregateKind;
133
134 /// Render every present element into `sink`, consuming the list.
135 #[doc(hidden)]
136 fn push_conditions(self, sink: &mut ConditionSink<'a, V>);
137
138 /// Render every present element into `sink`, borrowing the list.
139 #[doc(hidden)]
140 fn push_conditions_ref(&self, sink: &mut ConditionSink<'a, V>);
141}
142
143fn combine<'a, V, L>(conditions: L, separator: Token, empty: &'static str) -> SQL<'a, V>
144where
145 V: SQLParam + 'a,
146 L: ConditionList<'a, V>,
147{
148 let mut sink = ConditionSink::new(separator);
149 conditions.push_conditions(&mut sink);
150 sink.finish(empty)
151}
152
153fn combine_ref<'a, V, L>(conditions: &L, separator: Token, empty: &'static str) -> SQL<'a, V>
154where
155 V: SQLParam + 'a,
156 L: ConditionList<'a, V>,
157{
158 let mut sink = ConditionSink::new(separator);
159 conditions.push_conditions_ref(&mut sink);
160 sink.finish(empty)
161}
162
163// =============================================================================
164// all / any
165// =============================================================================
166
167/// Logical AND of every condition in a list.
168///
169/// The flat form of [`and`](super::and): `all((a, b, c))` renders
170/// `(a AND b AND c)` instead of nesting `and(a, and(b, c))`. `None` elements
171/// are skipped, and a list with no present element renders as `TRUE`.
172///
173/// A bare tuple already means the same thing in condition position
174/// (`.r#where((a, b, c))`); reach for `all` where a tuple would be read as a
175/// column list instead — most notably a join's `ON` condition, which accepts
176/// any SQL fragment rather than a typed condition.
177///
178/// ```rust
179/// # let _ = r####"
180/// use drizzle_core::expr::{all, eq, gt};
181///
182/// all((eq(users.active, true), gt(users.age, 18)))
183/// // ("users"."active" = ? AND "users"."age" > ?)
184/// # "####;
185/// ```
186#[allow(clippy::type_complexity)]
187pub fn all<'a, V, L>(
188 conditions: L,
189) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, L::Nullable, L::Aggregate>
190where
191 V: SQLParam + 'a,
192 L: ConditionList<'a, V>,
193{
194 SQLExpr::new(combine(conditions, Token::AND, EMPTY_CONJUNCTION))
195}
196
197/// Logical OR of every condition in a list.
198///
199/// The flat form of [`or`](super::or): `any((a, b, c))` renders
200/// `(a OR b OR c)` instead of nesting `or(a, or(b, c))`. `None` elements are
201/// skipped, and a list with no present element renders as `FALSE` — an empty
202/// disjunction matches nothing, which fails closed rather than quietly
203/// dropping the filter.
204///
205/// ```rust
206/// # let _ = r####"
207/// use drizzle_core::expr::{any, eq};
208///
209/// any((eq(users.role, "admin"), eq(users.role, "moderator")))
210/// // ("users"."role" = ? OR "users"."role" = ?)
211/// # "####;
212/// ```
213#[allow(clippy::type_complexity)]
214pub fn any<'a, V, L>(
215 conditions: L,
216) -> SQLExpr<'a, V, <V::DialectMarker as DialectTypes>::Bool, L::Nullable, L::Aggregate>
217where
218 V: SQLParam + 'a,
219 L: ConditionList<'a, V>,
220{
221 SQLExpr::new(combine(conditions, Token::OR, EMPTY_DISJUNCTION))
222}
223
224// =============================================================================
225// Tuple implementations
226// =============================================================================
227
228/// `ConditionList` for a 1-tuple: the markers are the single element's.
229macro_rules! impl_condition_one {
230 ($T:ident, $i:tt) => {
231 impl<'a, V, $T> ConditionList<'a, V> for ($T,)
232 where
233 V: SQLParam + 'a,
234 $T: Expr<'a, V>,
235 <$T as Expr<'a, V>>::SQLType: BooleanLike,
236 {
237 type Nullable = <$T as Expr<'a, V>>::Nullable;
238 type Aggregate = <$T as Expr<'a, V>>::Aggregate;
239
240 fn push_conditions(self, sink: &mut ConditionSink<'a, V>) {
241 sink.push(self.$i.into_condition_sql());
242 }
243
244 fn push_conditions_ref(&self, sink: &mut ConditionSink<'a, V>) {
245 sink.push(self.$i.to_condition_sql());
246 }
247 }
248 };
249}
250
251/// `ConditionList` for an N-tuple: delegate the marker fold to the
252/// (N-1)-tuple and combine it with the last element, mirroring how
253/// `RowColumnList` folds its column lists.
254macro_rules! impl_condition_many {
255 ([$($all:ident),+] [$($i:tt),+] [$($prev:ident),+] $last:ident) => {
256 impl<'a, V, $($all),+> ConditionList<'a, V> for ($($all,)+)
257 where
258 V: SQLParam + 'a,
259 $($all: Expr<'a, V>,)+
260 <$last as Expr<'a, V>>::SQLType: BooleanLike,
261 ($($prev,)+): ConditionList<'a, V>,
262 <($($prev,)+) as ConditionList<'a, V>>::Nullable:
263 NullOr<<$last as Expr<'a, V>>::Nullable>,
264 <($($prev,)+) as ConditionList<'a, V>>::Aggregate:
265 AggOr<<$last as Expr<'a, V>>::Aggregate>,
266 {
267 type Nullable = <<($($prev,)+) as ConditionList<'a, V>>::Nullable
268 as NullOr<<$last as Expr<'a, V>>::Nullable>>::Output;
269 type Aggregate = <<($($prev,)+) as ConditionList<'a, V>>::Aggregate
270 as AggOr<<$last as Expr<'a, V>>::Aggregate>>::Output;
271
272 fn push_conditions(self, sink: &mut ConditionSink<'a, V>) {
273 $( sink.push(self.$i.into_condition_sql()); )+
274 }
275
276 fn push_conditions_ref(&self, sink: &mut ConditionSink<'a, V>) {
277 $( sink.push(self.$i.to_condition_sql()); )+
278 }
279 }
280 };
281}
282
283/// Recursive accumulator splitting the last element off the type list while
284/// carrying the full type and index lists through to the impl.
285macro_rules! impl_condition_split {
286 ([$A:ident] [$i:tt] [] $only:ident) => {
287 impl_condition_one!($A, $i);
288 };
289 ([$($all:ident),+] [$($i:tt),+] [$($prev:ident),+] $last:ident) => {
290 impl_condition_many!([$($all),+] [$($i),+] [$($prev),+] $last);
291 };
292 ([$($all:ident),+] [$($i:tt),+] [] $head:ident, $($rest:ident),+) => {
293 impl_condition_split!([$($all),+] [$($i),+] [$head] $($rest),+);
294 };
295 ([$($all:ident),+] [$($i:tt),+] [$($prev:ident),+] $head:ident, $($rest:ident),+) => {
296 impl_condition_split!([$($all),+] [$($i),+] [$($prev),+, $head] $($rest),+);
297 };
298}
299
300/// `Expr` for a tuple of conditions: the tuple *is* the conjunction.
301///
302/// The tuple's `ToSQL` impl still renders a comma-separated list — that is what
303/// a SELECT or GROUP BY list needs — so the conjunction is produced by the
304/// expression-rendering hooks, which every condition site goes through.
305macro_rules! impl_condition_expr {
306 ($($T:ident),+) => {
307 impl<'a, V, $($T),+> Expr<'a, V> for ($($T,)+)
308 where
309 V: SQLParam + 'a,
310 Self: ConditionList<'a, V> + crate::traits::ToSQL<'a, V>,
311 {
312 type SQLType = crate::types::Conjunction;
313 type Nullable = <Self as ConditionList<'a, V>>::Nullable;
314 type Aggregate = <Self as ConditionList<'a, V>>::Aggregate;
315
316 fn to_expr_sql(&self) -> SQL<'a, V> {
317 combine_ref(self, Token::AND, EMPTY_CONJUNCTION)
318 }
319
320 fn into_expr_sql(self) -> SQL<'a, V> {
321 combine(self, Token::AND, EMPTY_CONJUNCTION)
322 }
323 }
324 };
325}
326
327/// Callback for `with_col_sizes_*!`: seals the tuple and generates its
328/// `ConditionList` impl.
329macro_rules! impl_condition_tuple {
330 ($($T:ident),+; $($i:tt),+) => {
331 impl<$($T),+> sealed::Sealed for ($($T,)+) {}
332 impl_condition_split!([$($T),+] [$($i),+] [] $($T),+);
333 };
334}
335
336/// Callback for `with_col_sizes_8!`: makes a tuple usable as an expression.
337macro_rules! impl_condition_tuple_expr {
338 ($($T:ident),+; $($i:tt),+) => {
339 impl_condition_expr!($($T),+);
340 };
341}
342
343with_col_sizes_8!(impl_condition_tuple);
344
345// Only the first ladder rung gets an `Expr` impl. `Expr` sits at the centre of
346// the trait graph — every literal, reference, `Option`, and column competes as
347// a candidate — and adding tuple candidates past arity 8 makes trait selection
348// blow past any usable memory budget while rustc well-formedness-checks the
349// nested marker fold. Longer lists still combine through `all`/`any`, which
350// need only `ConditionList`, or by nesting tuples.
351with_col_sizes_8!(impl_condition_tuple_expr);
352
353// The ladder stops at 16 even when `col32` and above are enabled. The marker
354// fold nests one projection per rung, and past 16 rungs rustc exhausts memory
355// well-formedness-checking the impls. Column lists need the higher rungs
356// because tables get wide; condition lists do not — `all`/`any` and nested
357// tuples cover anything longer, and produce the same flat AND.
358#[cfg(any(
359 feature = "col16",
360 feature = "col32",
361 feature = "col64",
362 feature = "col128",
363 feature = "col200"
364))]
365with_col_sizes_16!(impl_condition_tuple);