Skip to main content

reinhardt_query/expr/
condition.rs

1//! Condition system for WHERE and HAVING clauses.
2//!
3//! This module provides [`Condition`] and [`Cond`] for building complex
4//! filter conditions.
5
6use super::simple_expr::SimpleExpr;
7use crate::types::LogicalChainOper;
8
9/// Type of condition combination.
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
11pub enum ConditionType {
12	/// All conditions must be true (AND)
13	#[default]
14	All,
15	/// Any condition must be true (OR)
16	Any,
17}
18
19/// A single condition expression in a condition chain.
20#[derive(Debug, Clone)]
21pub enum ConditionExpression {
22	/// A simple expression
23	SimpleExpr(SimpleExpr),
24	/// A nested condition
25	Condition(Condition),
26}
27
28/// A condition chain for WHERE or HAVING clauses.
29///
30/// # Example
31///
32/// ```rust,ignore
33/// use reinhardt_query::{Cond, Expr};
34///
35/// // All conditions (AND)
36/// let cond = Cond::all()
37///     .add(Expr::col("active").eq(true))
38///     .add(Expr::col("age").gte(18));
39///
40/// // Any condition (OR)
41/// let cond = Cond::any()
42///     .add(Expr::col("role").eq("admin"))
43///     .add(Expr::col("role").eq("moderator"));
44///
45/// // Nested conditions
46/// let cond = Cond::all()
47///     .add(Expr::col("verified").eq(true))
48///     .add(Cond::any()
49///         .add(Expr::col("role").eq("admin"))
50///         .add(Expr::col("role").eq("moderator")));
51/// ```
52#[derive(Debug, Clone, Default)]
53pub struct Condition {
54	/// Type of condition chain (AND or OR)
55	pub condition_type: ConditionType,
56	/// Whether to negate the entire condition
57	pub negate: bool,
58	/// The conditions in this chain
59	pub conditions: Vec<ConditionExpression>,
60}
61
62impl Condition {
63	/// Create a new empty condition with the specified type.
64	pub fn new(condition_type: ConditionType) -> Self {
65		Self {
66			condition_type,
67			negate: false,
68			conditions: Vec::new(),
69		}
70	}
71
72	/// Create a new condition that requires all sub-conditions (AND).
73	pub fn all() -> Self {
74		Self::new(ConditionType::All)
75	}
76
77	/// Create a new condition that requires any sub-condition (OR).
78	pub fn any() -> Self {
79		Self::new(ConditionType::Any)
80	}
81
82	/// Add a condition expression.
83	#[must_use]
84	// Intentional builder-pattern method, not std::ops::Add
85	#[allow(clippy::should_implement_trait)]
86	pub fn add<C>(mut self, condition: C) -> Self
87	where
88		C: IntoCondition,
89	{
90		self.conditions.push(condition.into_condition_expression());
91		self
92	}
93
94	/// Add a condition only if the option is Some.
95	#[must_use]
96	pub fn add_option<C>(self, condition: Option<C>) -> Self
97	where
98		C: IntoCondition,
99	{
100		if let Some(c) = condition {
101			self.add(c)
102		} else {
103			self
104		}
105	}
106
107	/// Negate the entire condition.
108	#[must_use]
109	// Intentional builder-pattern method, not std::ops::Not
110	#[allow(clippy::should_implement_trait)]
111	pub fn not(mut self) -> Self {
112		self.negate = !self.negate;
113		self
114	}
115
116	/// Returns true if this condition has no sub-conditions.
117	pub fn is_empty(&self) -> bool {
118		self.conditions.is_empty()
119	}
120
121	/// Returns the number of sub-conditions.
122	pub fn len(&self) -> usize {
123		self.conditions.len()
124	}
125
126	/// Returns the logical operator for this condition type.
127	pub fn logical_oper(&self) -> LogicalChainOper {
128		match self.condition_type {
129			ConditionType::All => LogicalChainOper::And,
130			ConditionType::Any => LogicalChainOper::Or,
131		}
132	}
133}
134
135/// Helper for creating conditions.
136///
137/// This is a convenience wrapper around [`Condition`].
138pub struct Cond;
139
140impl Cond {
141	/// Create a condition that requires all sub-conditions (AND).
142	///
143	/// # Example
144	///
145	/// ```rust,ignore
146	/// use reinhardt_query::{Cond, Expr};
147	///
148	/// let cond = Cond::all()
149	///     .add(Expr::col("active").eq(true))
150	///     .add(Expr::col("verified").eq(true));
151	/// ```
152	pub fn all() -> Condition {
153		Condition::all()
154	}
155
156	/// Create a condition that requires any sub-condition (OR).
157	///
158	/// # Example
159	///
160	/// ```rust,ignore
161	/// use reinhardt_query::{Cond, Expr};
162	///
163	/// let cond = Cond::any()
164	///     .add(Expr::col("role").eq("admin"))
165	///     .add(Expr::col("role").eq("moderator"));
166	/// ```
167	pub fn any() -> Condition {
168		Condition::any()
169	}
170}
171
172/// Trait for types that can be converted into a condition expression.
173pub trait IntoCondition {
174	/// Convert into a ConditionExpression.
175	fn into_condition_expression(self) -> ConditionExpression;
176
177	/// Convert into a Condition (wrapping if necessary).
178	fn into_condition(self) -> Condition
179	where
180		Self: Sized,
181	{
182		let expr = self.into_condition_expression();
183		match expr {
184			ConditionExpression::Condition(c) => c,
185			ConditionExpression::SimpleExpr(e) => Condition::all().add(e),
186		}
187	}
188}
189
190impl IntoCondition for Condition {
191	fn into_condition_expression(self) -> ConditionExpression {
192		ConditionExpression::Condition(self)
193	}
194
195	fn into_condition(self) -> Condition {
196		self
197	}
198}
199
200impl IntoCondition for SimpleExpr {
201	fn into_condition_expression(self) -> ConditionExpression {
202		ConditionExpression::SimpleExpr(self)
203	}
204}
205
206impl IntoCondition for super::expr::Expr {
207	fn into_condition_expression(self) -> ConditionExpression {
208		ConditionExpression::SimpleExpr(self.into_simple_expr())
209	}
210}
211
212/// Holder for conditions in query builders.
213///
214/// This is used internally by query builders to hold WHERE and HAVING clauses.
215#[derive(Debug, Clone, Default)]
216pub struct ConditionHolder {
217	/// The conditions
218	pub conditions: Vec<ConditionExpression>,
219}
220
221impl ConditionHolder {
222	/// Create a new empty condition holder.
223	pub fn new() -> Self {
224		Self::default()
225	}
226
227	/// Add a condition with AND.
228	pub fn add_and<C>(&mut self, condition: C)
229	where
230		C: IntoCondition,
231	{
232		self.conditions.push(condition.into_condition_expression());
233	}
234
235	/// Add a condition with OR (wraps existing conditions).
236	pub fn add_or<C>(&mut self, condition: C)
237	where
238		C: IntoCondition,
239	{
240		// If we have existing conditions, wrap them in an OR
241		if !self.conditions.is_empty() {
242			let existing = std::mem::take(&mut self.conditions);
243			let mut or_cond = Condition::any();
244			for c in existing {
245				or_cond.conditions.push(c);
246			}
247			or_cond
248				.conditions
249				.push(condition.into_condition_expression());
250			self.conditions
251				.push(ConditionExpression::Condition(or_cond));
252		} else {
253			self.conditions.push(condition.into_condition_expression());
254		}
255	}
256
257	/// Set all conditions from a Condition.
258	pub fn set_condition(&mut self, condition: Condition) {
259		self.conditions = vec![ConditionExpression::Condition(condition)];
260	}
261
262	/// Returns true if there are no conditions.
263	pub fn is_empty(&self) -> bool {
264		self.conditions.is_empty()
265	}
266
267	/// Returns the number of conditions.
268	pub fn len(&self) -> usize {
269		self.conditions.len()
270	}
271
272	/// Build into a single Condition.
273	pub fn into_condition(mut self) -> Option<Condition> {
274		if self.conditions.is_empty() {
275			return None;
276		}
277
278		if self.conditions.len() == 1 {
279			// Take the single condition out
280			match self.conditions.pop() {
281				Some(ConditionExpression::Condition(c)) => return Some(c),
282				Some(ConditionExpression::SimpleExpr(e)) => {
283					return Some(Condition::all().add(e));
284				}
285				None => return None,
286			}
287		}
288
289		let mut cond = Condition::all();
290		cond.conditions = self.conditions;
291		Some(cond)
292	}
293}
294
295/// Create an ALL (AND) condition from multiple expressions.
296///
297/// # Example
298///
299/// ```rust,ignore
300/// use reinhardt_query::{all, Expr};
301///
302/// let cond = all![
303///     Expr::col("active").eq(true),
304///     Expr::col("verified").eq(true),
305/// ];
306/// ```
307#[macro_export]
308macro_rules! all {
309    ($($expr:expr),* $(,)?) => {
310        {
311            let mut cond = $crate::expr::Cond::all();
312            $(
313                cond = cond.add($expr);
314            )*
315            cond
316        }
317    };
318}
319
320/// Create an ANY (OR) condition from multiple expressions.
321///
322/// # Example
323///
324/// ```rust,ignore
325/// use reinhardt_query::{any, Expr};
326///
327/// let cond = any![
328///     Expr::col("role").eq("admin"),
329///     Expr::col("role").eq("moderator"),
330/// ];
331/// ```
332#[macro_export]
333macro_rules! any {
334    ($($expr:expr),* $(,)?) => {
335        {
336            let mut cond = $crate::expr::Cond::any();
337            $(
338                cond = cond.add($expr);
339            )*
340            cond
341        }
342    };
343}
344
345#[cfg(test)]
346mod tests {
347	use super::*;
348	use crate::expr::{Expr, ExprTrait};
349	use rstest::rstest;
350
351	#[rstest]
352	fn test_condition_all() {
353		let cond = Cond::all()
354			.add(Expr::col("active").eq(true))
355			.add(Expr::col("verified").eq(true));
356
357		assert_eq!(cond.condition_type, ConditionType::All);
358		assert_eq!(cond.len(), 2);
359		assert!(!cond.is_empty());
360	}
361
362	#[rstest]
363	fn test_condition_any() {
364		let cond = Cond::any()
365			.add(Expr::col("role").eq("admin"))
366			.add(Expr::col("role").eq("moderator"));
367
368		assert_eq!(cond.condition_type, ConditionType::Any);
369		assert_eq!(cond.len(), 2);
370	}
371
372	#[rstest]
373	fn test_condition_nested() {
374		let cond = Cond::all().add(Expr::col("verified").eq(true)).add(
375			Cond::any()
376				.add(Expr::col("role").eq("admin"))
377				.add(Expr::col("role").eq("moderator")),
378		);
379
380		assert_eq!(cond.len(), 2);
381	}
382
383	#[rstest]
384	fn test_condition_not() {
385		let cond = Cond::all().add(Expr::col("deleted").eq(true)).not();
386
387		assert!(cond.negate);
388	}
389
390	#[rstest]
391	fn test_condition_add_option() {
392		let filter_active: Option<bool> = Some(true);
393		let filter_role: Option<&str> = None;
394
395		let cond = Cond::all()
396			.add_option(filter_active.map(|v| Expr::col("active").eq(v)))
397			.add_option(filter_role.map(|v| Expr::col("role").eq(v)));
398
399		assert_eq!(cond.len(), 1); // Only active filter added
400	}
401
402	#[rstest]
403	fn test_condition_empty() {
404		let cond = Cond::all();
405		assert!(cond.is_empty());
406		assert_eq!(cond.len(), 0);
407	}
408
409	#[rstest]
410	fn test_condition_holder() {
411		let mut holder = ConditionHolder::new();
412		assert!(holder.is_empty());
413
414		holder.add_and(Expr::col("active").eq(true));
415		holder.add_and(Expr::col("verified").eq(true));
416
417		assert!(!holder.is_empty());
418		assert_eq!(holder.len(), 2);
419	}
420
421	#[rstest]
422	fn test_condition_holder_into_condition() {
423		let mut holder = ConditionHolder::new();
424		holder.add_and(Expr::col("active").eq(true));
425		holder.add_and(Expr::col("verified").eq(true));
426
427		let cond = holder.into_condition();
428		assert!(cond.is_some());
429	}
430
431	#[rstest]
432	fn test_all_macro() {
433		let cond = all![Expr::col("a").eq(1), Expr::col("b").eq(2),];
434
435		assert_eq!(cond.condition_type, ConditionType::All);
436		assert_eq!(cond.len(), 2);
437	}
438
439	#[rstest]
440	fn test_any_macro() {
441		let cond = any![Expr::col("a").eq(1), Expr::col("b").eq(2),];
442
443		assert_eq!(cond.condition_type, ConditionType::Any);
444		assert_eq!(cond.len(), 2);
445	}
446
447	#[rstest]
448	fn test_logical_oper() {
449		assert_eq!(Cond::all().logical_oper(), LogicalChainOper::And);
450		assert_eq!(Cond::any().logical_oper(), LogicalChainOper::Or);
451	}
452}