Skip to main content

reinhardt_query/expr/
expr_trait.rs

1//! ExprTrait - Expression operations trait.
2//!
3//! This module provides [`ExprTrait`], which defines all the operations
4//! that can be performed on expressions (comparisons, logical ops, etc.).
5
6use super::simple_expr::{Keyword, SimpleExpr};
7use crate::types::{BinOper, UnOper};
8use crate::value::Value;
9
10/// Trait for expression operations.
11///
12/// This trait provides methods for building complex expressions through
13/// operator chaining. It is implemented for [`Expr`](crate::expr::Expr) and [`SimpleExpr`].
14///
15/// # Example
16///
17/// ```rust,ignore
18/// use reinhardt_query::{Expr, ExprTrait};
19///
20/// // Comparison
21/// let expr = Expr::col("age").gte(18);
22///
23/// // Logical operations
24/// let expr = Expr::col("active").eq(true).and(Expr::col("verified").eq(true));
25///
26/// // Arithmetic
27/// let expr = Expr::col("price").mul(Expr::col("quantity"));
28/// ```
29// Expression trait methods consume self for builder-pattern chaining,
30// so is_*/as_* methods intentionally take self by value.
31#[allow(clippy::wrong_self_convention)]
32pub trait ExprTrait: Sized {
33	/// Build the final SimpleExpr.
34	fn into_simple_expr(self) -> SimpleExpr;
35
36	// =========================================================================
37	// Comparison operations
38	// =========================================================================
39
40	/// Equal (`=`).
41	///
42	/// # Example
43	///
44	/// ```rust,ignore
45	/// Expr::col("name").eq("Alice")
46	/// // Generates: "name" = 'Alice'
47	/// ```
48	fn eq<V>(self, v: V) -> SimpleExpr
49	where
50		V: Into<SimpleExpr>,
51	{
52		SimpleExpr::Binary(
53			Box::new(self.into_simple_expr()),
54			BinOper::Equal,
55			Box::new(v.into()),
56		)
57	}
58
59	/// Not equal (`<>`).
60	fn ne<V>(self, v: V) -> SimpleExpr
61	where
62		V: Into<SimpleExpr>,
63	{
64		SimpleExpr::Binary(
65			Box::new(self.into_simple_expr()),
66			BinOper::NotEqual,
67			Box::new(v.into()),
68		)
69	}
70
71	/// Less than (`<`).
72	fn lt<V>(self, v: V) -> SimpleExpr
73	where
74		V: Into<SimpleExpr>,
75	{
76		SimpleExpr::Binary(
77			Box::new(self.into_simple_expr()),
78			BinOper::SmallerThan,
79			Box::new(v.into()),
80		)
81	}
82
83	/// Less than or equal (`<=`).
84	fn lte<V>(self, v: V) -> SimpleExpr
85	where
86		V: Into<SimpleExpr>,
87	{
88		SimpleExpr::Binary(
89			Box::new(self.into_simple_expr()),
90			BinOper::SmallerThanOrEqual,
91			Box::new(v.into()),
92		)
93	}
94
95	/// Greater than (`>`).
96	fn gt<V>(self, v: V) -> SimpleExpr
97	where
98		V: Into<SimpleExpr>,
99	{
100		SimpleExpr::Binary(
101			Box::new(self.into_simple_expr()),
102			BinOper::GreaterThan,
103			Box::new(v.into()),
104		)
105	}
106
107	/// Greater than or equal (`>=`).
108	fn gte<V>(self, v: V) -> SimpleExpr
109	where
110		V: Into<SimpleExpr>,
111	{
112		SimpleExpr::Binary(
113			Box::new(self.into_simple_expr()),
114			BinOper::GreaterThanOrEqual,
115			Box::new(v.into()),
116		)
117	}
118
119	// =========================================================================
120	// NULL checks
121	// =========================================================================
122
123	/// IS NULL.
124	fn is_null(self) -> SimpleExpr {
125		SimpleExpr::Binary(
126			Box::new(self.into_simple_expr()),
127			BinOper::Is,
128			Box::new(SimpleExpr::Constant(Keyword::Null)),
129		)
130	}
131
132	/// IS NOT NULL.
133	fn is_not_null(self) -> SimpleExpr {
134		SimpleExpr::Binary(
135			Box::new(self.into_simple_expr()),
136			BinOper::IsNot,
137			Box::new(SimpleExpr::Constant(Keyword::Null)),
138		)
139	}
140
141	// =========================================================================
142	// Range operations
143	// =========================================================================
144
145	/// BETWEEN.
146	///
147	/// # Example
148	///
149	/// ```rust,ignore
150	/// Expr::col("age").between(18, 65)
151	/// // Generates: "age" BETWEEN 18 AND 65
152	/// ```
153	fn between<A, B>(self, a: A, b: B) -> SimpleExpr
154	where
155		A: Into<SimpleExpr>,
156		B: Into<SimpleExpr>,
157	{
158		SimpleExpr::Binary(
159			Box::new(self.into_simple_expr()),
160			BinOper::Between,
161			Box::new(SimpleExpr::Tuple(vec![a.into(), b.into()])),
162		)
163	}
164
165	/// NOT BETWEEN.
166	fn not_between<A, B>(self, a: A, b: B) -> SimpleExpr
167	where
168		A: Into<SimpleExpr>,
169		B: Into<SimpleExpr>,
170	{
171		SimpleExpr::Binary(
172			Box::new(self.into_simple_expr()),
173			BinOper::NotBetween,
174			Box::new(SimpleExpr::Tuple(vec![a.into(), b.into()])),
175		)
176	}
177
178	// =========================================================================
179	// Set membership
180	// =========================================================================
181
182	/// IN.
183	///
184	/// # Example
185	///
186	/// ```rust,ignore
187	/// Expr::col("status").is_in(["active", "pending"])
188	/// // Generates: "status" IN ('active', 'pending')
189	/// ```
190	fn is_in<I, V>(self, values: I) -> SimpleExpr
191	where
192		I: IntoIterator<Item = V>,
193		V: Into<SimpleExpr>,
194	{
195		SimpleExpr::Binary(
196			Box::new(self.into_simple_expr()),
197			BinOper::In,
198			Box::new(SimpleExpr::Tuple(
199				values.into_iter().map(|v| v.into()).collect(),
200			)),
201		)
202	}
203
204	/// NOT IN.
205	fn is_not_in<I, V>(self, values: I) -> SimpleExpr
206	where
207		I: IntoIterator<Item = V>,
208		V: Into<SimpleExpr>,
209	{
210		SimpleExpr::Binary(
211			Box::new(self.into_simple_expr()),
212			BinOper::NotIn,
213			Box::new(SimpleExpr::Tuple(
214				values.into_iter().map(|v| v.into()).collect(),
215			)),
216		)
217	}
218
219	// =========================================================================
220	// Pattern matching
221	// =========================================================================
222
223	/// LIKE.
224	///
225	/// # Example
226	///
227	/// ```rust,ignore
228	/// Expr::col("name").like("%john%")
229	/// // Generates: "name" LIKE '%john%'
230	/// ```
231	fn like<V>(self, pattern: V) -> SimpleExpr
232	where
233		V: Into<SimpleExpr>,
234	{
235		SimpleExpr::Binary(
236			Box::new(self.into_simple_expr()),
237			BinOper::Like,
238			Box::new(pattern.into()),
239		)
240	}
241
242	/// NOT LIKE.
243	fn not_like<V>(self, pattern: V) -> SimpleExpr
244	where
245		V: Into<SimpleExpr>,
246	{
247		SimpleExpr::Binary(
248			Box::new(self.into_simple_expr()),
249			BinOper::NotLike,
250			Box::new(pattern.into()),
251		)
252	}
253
254	/// ILIKE (case-insensitive LIKE, PostgreSQL).
255	fn ilike<V>(self, pattern: V) -> SimpleExpr
256	where
257		V: Into<SimpleExpr>,
258	{
259		SimpleExpr::Binary(
260			Box::new(self.into_simple_expr()),
261			BinOper::ILike,
262			Box::new(pattern.into()),
263		)
264	}
265
266	/// NOT ILIKE (PostgreSQL).
267	fn not_ilike<V>(self, pattern: V) -> SimpleExpr
268	where
269		V: Into<SimpleExpr>,
270	{
271		SimpleExpr::Binary(
272			Box::new(self.into_simple_expr()),
273			BinOper::NotILike,
274			Box::new(pattern.into()),
275		)
276	}
277
278	/// Helper for LIKE with prefix wildcard.
279	fn starts_with<S>(self, prefix: S) -> SimpleExpr
280	where
281		S: Into<String>,
282	{
283		let pattern = format!("{}%", prefix.into());
284		self.like(Value::String(Some(Box::new(pattern))))
285	}
286
287	/// Helper for LIKE with suffix wildcard.
288	fn ends_with<S>(self, suffix: S) -> SimpleExpr
289	where
290		S: Into<String>,
291	{
292		let pattern = format!("%{}", suffix.into());
293		self.like(Value::String(Some(Box::new(pattern))))
294	}
295
296	/// Helper for LIKE with both wildcards.
297	fn contains<S>(self, substring: S) -> SimpleExpr
298	where
299		S: Into<String>,
300	{
301		let pattern = format!("%{}%", substring.into());
302		self.like(Value::String(Some(Box::new(pattern))))
303	}
304
305	// =========================================================================
306	// Logical operations
307	// =========================================================================
308
309	/// AND.
310	fn and<E>(self, other: E) -> SimpleExpr
311	where
312		E: Into<SimpleExpr>,
313	{
314		SimpleExpr::Binary(
315			Box::new(self.into_simple_expr()),
316			BinOper::And,
317			Box::new(other.into()),
318		)
319	}
320
321	/// OR.
322	fn or<E>(self, other: E) -> SimpleExpr
323	where
324		E: Into<SimpleExpr>,
325	{
326		SimpleExpr::Binary(
327			Box::new(self.into_simple_expr()),
328			BinOper::Or,
329			Box::new(other.into()),
330		)
331	}
332
333	/// NOT (unary).
334	fn not(self) -> SimpleExpr {
335		SimpleExpr::Unary(UnOper::Not, Box::new(self.into_simple_expr()))
336	}
337
338	// =========================================================================
339	// Arithmetic operations
340	// =========================================================================
341
342	/// Addition (`+`).
343	fn add<V>(self, v: V) -> SimpleExpr
344	where
345		V: Into<SimpleExpr>,
346	{
347		SimpleExpr::Binary(
348			Box::new(self.into_simple_expr()),
349			BinOper::Add,
350			Box::new(v.into()),
351		)
352	}
353
354	/// Subtraction (`-`).
355	fn sub<V>(self, v: V) -> SimpleExpr
356	where
357		V: Into<SimpleExpr>,
358	{
359		SimpleExpr::Binary(
360			Box::new(self.into_simple_expr()),
361			BinOper::Sub,
362			Box::new(v.into()),
363		)
364	}
365
366	/// Multiplication (`*`).
367	fn mul<V>(self, v: V) -> SimpleExpr
368	where
369		V: Into<SimpleExpr>,
370	{
371		SimpleExpr::Binary(
372			Box::new(self.into_simple_expr()),
373			BinOper::Mul,
374			Box::new(v.into()),
375		)
376	}
377
378	/// Division (`/`).
379	fn div<V>(self, v: V) -> SimpleExpr
380	where
381		V: Into<SimpleExpr>,
382	{
383		SimpleExpr::Binary(
384			Box::new(self.into_simple_expr()),
385			BinOper::Div,
386			Box::new(v.into()),
387		)
388	}
389
390	/// Modulo (`%`).
391	fn modulo<V>(self, v: V) -> SimpleExpr
392	where
393		V: Into<SimpleExpr>,
394	{
395		SimpleExpr::Binary(
396			Box::new(self.into_simple_expr()),
397			BinOper::Mod,
398			Box::new(v.into()),
399		)
400	}
401
402	// =========================================================================
403	// Bitwise operations
404	// =========================================================================
405
406	/// Bitwise AND (`&`).
407	fn bit_and<V>(self, v: V) -> SimpleExpr
408	where
409		V: Into<SimpleExpr>,
410	{
411		SimpleExpr::Binary(
412			Box::new(self.into_simple_expr()),
413			BinOper::BitAnd,
414			Box::new(v.into()),
415		)
416	}
417
418	/// Bitwise OR (`|`).
419	fn bit_or<V>(self, v: V) -> SimpleExpr
420	where
421		V: Into<SimpleExpr>,
422	{
423		SimpleExpr::Binary(
424			Box::new(self.into_simple_expr()),
425			BinOper::BitOr,
426			Box::new(v.into()),
427		)
428	}
429
430	/// Left shift (`<<`).
431	fn left_shift<V>(self, v: V) -> SimpleExpr
432	where
433		V: Into<SimpleExpr>,
434	{
435		SimpleExpr::Binary(
436			Box::new(self.into_simple_expr()),
437			BinOper::LShift,
438			Box::new(v.into()),
439		)
440	}
441
442	/// Right shift (`>>`).
443	fn right_shift<V>(self, v: V) -> SimpleExpr
444	where
445		V: Into<SimpleExpr>,
446	{
447		SimpleExpr::Binary(
448			Box::new(self.into_simple_expr()),
449			BinOper::RShift,
450			Box::new(v.into()),
451		)
452	}
453
454	// =========================================================================
455	// Type casting
456	// =========================================================================
457
458	/// CAST expression.
459	///
460	/// # Example
461	///
462	/// ```rust,ignore
463	/// Expr::col("age").cast_as("TEXT")
464	/// // Generates: CAST("age" AS TEXT)
465	/// ```
466	fn cast_as<T>(self, type_name: T) -> SimpleExpr
467	where
468		T: crate::types::IntoIden,
469	{
470		SimpleExpr::Cast(Box::new(self.into_simple_expr()), type_name.into_iden())
471	}
472
473	/// AS ENUM expression (PostgreSQL).
474	fn as_enum<T>(self, type_name: T) -> SimpleExpr
475	where
476		T: crate::types::IntoIden,
477	{
478		SimpleExpr::AsEnum(type_name.into_iden(), Box::new(self.into_simple_expr()))
479	}
480}
481
482// Implement ExprTrait for SimpleExpr
483impl ExprTrait for SimpleExpr {
484	fn into_simple_expr(self) -> SimpleExpr {
485		self
486	}
487}
488
489// Implement ExprTrait for Expr
490impl ExprTrait for super::expr::Expr {
491	fn into_simple_expr(self) -> SimpleExpr {
492		self.into_simple_expr()
493	}
494}
495
496#[cfg(test)]
497mod tests {
498	use super::*;
499	use crate::expr::Expr;
500	use rstest::rstest;
501
502	#[rstest]
503	fn test_eq() {
504		let expr = Expr::col("name").eq("Alice");
505		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Equal, _)));
506	}
507
508	#[rstest]
509	fn test_ne() {
510		let expr = Expr::col("name").ne("Bob");
511		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotEqual, _)));
512	}
513
514	#[rstest]
515	fn test_lt() {
516		let expr = Expr::col("age").lt(18);
517		assert!(matches!(
518			expr,
519			SimpleExpr::Binary(_, BinOper::SmallerThan, _)
520		));
521	}
522
523	#[rstest]
524	fn test_lte() {
525		let expr = Expr::col("age").lte(65);
526		assert!(matches!(
527			expr,
528			SimpleExpr::Binary(_, BinOper::SmallerThanOrEqual, _)
529		));
530	}
531
532	#[rstest]
533	fn test_gt() {
534		let expr = Expr::col("age").gt(18);
535		assert!(matches!(
536			expr,
537			SimpleExpr::Binary(_, BinOper::GreaterThan, _)
538		));
539	}
540
541	#[rstest]
542	fn test_gte() {
543		let expr = Expr::col("age").gte(18);
544		assert!(matches!(
545			expr,
546			SimpleExpr::Binary(_, BinOper::GreaterThanOrEqual, _)
547		));
548	}
549
550	#[rstest]
551	fn test_is_null() {
552		let expr = Expr::col("deleted_at").is_null();
553		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Is, _)));
554	}
555
556	#[rstest]
557	fn test_is_not_null() {
558		let expr = Expr::col("name").is_not_null();
559		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::IsNot, _)));
560	}
561
562	#[rstest]
563	fn test_between() {
564		let expr = Expr::col("age").between(18, 65);
565		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Between, _)));
566	}
567
568	#[rstest]
569	fn test_not_between() {
570		let expr = Expr::col("age").not_between(0, 17);
571		assert!(matches!(
572			expr,
573			SimpleExpr::Binary(_, BinOper::NotBetween, _)
574		));
575	}
576
577	#[rstest]
578	fn test_is_in() {
579		let expr = Expr::col("status").is_in(["active", "pending"]);
580		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::In, _)));
581	}
582
583	#[rstest]
584	fn test_is_not_in() {
585		let expr = Expr::col("status").is_not_in(["deleted", "banned"]);
586		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotIn, _)));
587	}
588
589	#[rstest]
590	fn test_like() {
591		let expr = Expr::col("name").like("%john%");
592		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Like, _)));
593	}
594
595	#[rstest]
596	fn test_not_like() {
597		let expr = Expr::col("name").not_like("%admin%");
598		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotLike, _)));
599	}
600
601	#[rstest]
602	fn test_starts_with() {
603		let expr = Expr::col("name").starts_with("John");
604		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Like, _)));
605	}
606
607	#[rstest]
608	fn test_ends_with() {
609		let expr = Expr::col("email").ends_with("@example.com");
610		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Like, _)));
611	}
612
613	#[rstest]
614	fn test_contains() {
615		let expr = Expr::col("description").contains("important");
616		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Like, _)));
617	}
618
619	#[rstest]
620	fn test_and() {
621		let expr = Expr::col("active")
622			.eq(true)
623			.and(Expr::col("verified").eq(true));
624		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::And, _)));
625	}
626
627	#[rstest]
628	fn test_or() {
629		let expr = Expr::col("role")
630			.eq("admin")
631			.or(Expr::col("role").eq("moderator"));
632		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Or, _)));
633	}
634
635	#[rstest]
636	fn test_not() {
637		let expr = Expr::col("deleted").not();
638		assert!(matches!(expr, SimpleExpr::Unary(UnOper::Not, _)));
639	}
640
641	#[rstest]
642	fn test_add() {
643		let expr = Expr::col("price").add(10);
644		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Add, _)));
645	}
646
647	#[rstest]
648	fn test_sub() {
649		let expr = Expr::col("quantity").sub(1);
650		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Sub, _)));
651	}
652
653	#[rstest]
654	fn test_mul() {
655		let expr = Expr::col("price").mul(Expr::col("quantity"));
656		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Mul, _)));
657	}
658
659	#[rstest]
660	fn test_div() {
661		let expr = Expr::col("total").div(Expr::col("count"));
662		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Div, _)));
663	}
664
665	#[rstest]
666	fn test_modulo() {
667		let expr = Expr::col("value").modulo(2);
668		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Mod, _)));
669	}
670
671	#[rstest]
672	fn test_cast_as() {
673		let expr = Expr::col("age").cast_as("TEXT");
674		assert!(matches!(expr, SimpleExpr::Cast(_, _)));
675	}
676
677	#[rstest]
678	fn test_chained_operations() {
679		// Test complex expression chaining
680		let expr = Expr::col("age")
681			.gte(18)
682			.and(Expr::col("active").eq(true))
683			.and(Expr::col("verified").is_not_null());
684
685		assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::And, _)));
686	}
687}