1use super::simple_expr::{Keyword, SimpleExpr};
7use crate::types::{BinOper, UnOper};
8use crate::value::Value;
9
10fn escape_like_pattern(input: &str) -> String {
20 let mut escaped = String::with_capacity(input.len());
21 for ch in input.chars() {
22 match ch {
23 '\\' => escaped.push_str("\\\\"),
24 '%' => escaped.push_str("\\%"),
25 '_' => escaped.push_str("\\_"),
26 _ => escaped.push(ch),
27 }
28 }
29 escaped
30}
31
32fn like_with_escape(expr: SimpleExpr, pattern: String) -> SimpleExpr {
40 SimpleExpr::CustomWithExpr(
41 "? LIKE ? ESCAPE '\\'".to_string(),
42 vec![
43 expr,
44 SimpleExpr::Value(Value::String(Some(Box::new(pattern)))),
45 ],
46 )
47}
48
49#[allow(clippy::wrong_self_convention)]
71pub trait ExprTrait: Sized {
72 fn into_simple_expr(self) -> SimpleExpr;
74
75 fn eq<V>(self, v: V) -> SimpleExpr
88 where
89 V: Into<SimpleExpr>,
90 {
91 SimpleExpr::Binary(
92 Box::new(self.into_simple_expr()),
93 BinOper::Equal,
94 Box::new(v.into()),
95 )
96 }
97
98 fn ne<V>(self, v: V) -> SimpleExpr
100 where
101 V: Into<SimpleExpr>,
102 {
103 SimpleExpr::Binary(
104 Box::new(self.into_simple_expr()),
105 BinOper::NotEqual,
106 Box::new(v.into()),
107 )
108 }
109
110 fn lt<V>(self, v: V) -> SimpleExpr
112 where
113 V: Into<SimpleExpr>,
114 {
115 SimpleExpr::Binary(
116 Box::new(self.into_simple_expr()),
117 BinOper::SmallerThan,
118 Box::new(v.into()),
119 )
120 }
121
122 fn lte<V>(self, v: V) -> SimpleExpr
124 where
125 V: Into<SimpleExpr>,
126 {
127 SimpleExpr::Binary(
128 Box::new(self.into_simple_expr()),
129 BinOper::SmallerThanOrEqual,
130 Box::new(v.into()),
131 )
132 }
133
134 fn gt<V>(self, v: V) -> SimpleExpr
136 where
137 V: Into<SimpleExpr>,
138 {
139 SimpleExpr::Binary(
140 Box::new(self.into_simple_expr()),
141 BinOper::GreaterThan,
142 Box::new(v.into()),
143 )
144 }
145
146 fn gte<V>(self, v: V) -> SimpleExpr
148 where
149 V: Into<SimpleExpr>,
150 {
151 SimpleExpr::Binary(
152 Box::new(self.into_simple_expr()),
153 BinOper::GreaterThanOrEqual,
154 Box::new(v.into()),
155 )
156 }
157
158 fn is_null(self) -> SimpleExpr {
164 SimpleExpr::Binary(
165 Box::new(self.into_simple_expr()),
166 BinOper::Is,
167 Box::new(SimpleExpr::Constant(Keyword::Null)),
168 )
169 }
170
171 fn is_not_null(self) -> SimpleExpr {
173 SimpleExpr::Binary(
174 Box::new(self.into_simple_expr()),
175 BinOper::IsNot,
176 Box::new(SimpleExpr::Constant(Keyword::Null)),
177 )
178 }
179
180 fn between<A, B>(self, a: A, b: B) -> SimpleExpr
193 where
194 A: Into<SimpleExpr>,
195 B: Into<SimpleExpr>,
196 {
197 SimpleExpr::Binary(
198 Box::new(self.into_simple_expr()),
199 BinOper::Between,
200 Box::new(SimpleExpr::Tuple(vec![a.into(), b.into()])),
201 )
202 }
203
204 fn not_between<A, B>(self, a: A, b: B) -> SimpleExpr
206 where
207 A: Into<SimpleExpr>,
208 B: Into<SimpleExpr>,
209 {
210 SimpleExpr::Binary(
211 Box::new(self.into_simple_expr()),
212 BinOper::NotBetween,
213 Box::new(SimpleExpr::Tuple(vec![a.into(), b.into()])),
214 )
215 }
216
217 fn is_in<I, V>(self, values: I) -> SimpleExpr
232 where
233 I: IntoIterator<Item = V>,
234 V: Into<SimpleExpr>,
235 {
236 let collected: Vec<SimpleExpr> = values.into_iter().map(|v| v.into()).collect();
237 if collected.is_empty() {
238 return SimpleExpr::Constant(Keyword::False);
240 }
241 SimpleExpr::Binary(
242 Box::new(self.into_simple_expr()),
243 BinOper::In,
244 Box::new(SimpleExpr::Tuple(collected)),
245 )
246 }
247
248 fn is_not_in<I, V>(self, values: I) -> SimpleExpr
253 where
254 I: IntoIterator<Item = V>,
255 V: Into<SimpleExpr>,
256 {
257 let collected: Vec<SimpleExpr> = values.into_iter().map(|v| v.into()).collect();
258 if collected.is_empty() {
259 return SimpleExpr::Constant(Keyword::True);
261 }
262 SimpleExpr::Binary(
263 Box::new(self.into_simple_expr()),
264 BinOper::NotIn,
265 Box::new(SimpleExpr::Tuple(collected)),
266 )
267 }
268
269 fn like<V>(self, pattern: V) -> SimpleExpr
282 where
283 V: Into<SimpleExpr>,
284 {
285 SimpleExpr::Binary(
286 Box::new(self.into_simple_expr()),
287 BinOper::Like,
288 Box::new(pattern.into()),
289 )
290 }
291
292 fn not_like<V>(self, pattern: V) -> SimpleExpr
294 where
295 V: Into<SimpleExpr>,
296 {
297 SimpleExpr::Binary(
298 Box::new(self.into_simple_expr()),
299 BinOper::NotLike,
300 Box::new(pattern.into()),
301 )
302 }
303
304 fn ilike<V>(self, pattern: V) -> SimpleExpr
306 where
307 V: Into<SimpleExpr>,
308 {
309 SimpleExpr::Binary(
310 Box::new(self.into_simple_expr()),
311 BinOper::ILike,
312 Box::new(pattern.into()),
313 )
314 }
315
316 fn not_ilike<V>(self, pattern: V) -> SimpleExpr
318 where
319 V: Into<SimpleExpr>,
320 {
321 SimpleExpr::Binary(
322 Box::new(self.into_simple_expr()),
323 BinOper::NotILike,
324 Box::new(pattern.into()),
325 )
326 }
327
328 fn starts_with<S>(self, prefix: S) -> SimpleExpr
336 where
337 S: Into<String>,
338 {
339 let escaped = escape_like_pattern(&prefix.into());
340 let pattern = format!("{}%", escaped);
341 like_with_escape(self.into_simple_expr(), pattern)
342 }
343
344 fn ends_with<S>(self, suffix: S) -> SimpleExpr
351 where
352 S: Into<String>,
353 {
354 let escaped = escape_like_pattern(&suffix.into());
355 let pattern = format!("%{}", escaped);
356 like_with_escape(self.into_simple_expr(), pattern)
357 }
358
359 fn contains<S>(self, substring: S) -> SimpleExpr
366 where
367 S: Into<String>,
368 {
369 let escaped = escape_like_pattern(&substring.into());
370 let pattern = format!("%{}%", escaped);
371 like_with_escape(self.into_simple_expr(), pattern)
372 }
373
374 fn and<E>(self, other: E) -> SimpleExpr
380 where
381 E: Into<SimpleExpr>,
382 {
383 SimpleExpr::Binary(
384 Box::new(self.into_simple_expr()),
385 BinOper::And,
386 Box::new(other.into()),
387 )
388 }
389
390 fn or<E>(self, other: E) -> SimpleExpr
392 where
393 E: Into<SimpleExpr>,
394 {
395 SimpleExpr::Binary(
396 Box::new(self.into_simple_expr()),
397 BinOper::Or,
398 Box::new(other.into()),
399 )
400 }
401
402 fn not(self) -> SimpleExpr {
404 SimpleExpr::Unary(UnOper::Not, Box::new(self.into_simple_expr()))
405 }
406
407 fn add<V>(self, v: V) -> SimpleExpr
413 where
414 V: Into<SimpleExpr>,
415 {
416 SimpleExpr::Binary(
417 Box::new(self.into_simple_expr()),
418 BinOper::Add,
419 Box::new(v.into()),
420 )
421 }
422
423 fn sub<V>(self, v: V) -> SimpleExpr
425 where
426 V: Into<SimpleExpr>,
427 {
428 SimpleExpr::Binary(
429 Box::new(self.into_simple_expr()),
430 BinOper::Sub,
431 Box::new(v.into()),
432 )
433 }
434
435 fn mul<V>(self, v: V) -> SimpleExpr
437 where
438 V: Into<SimpleExpr>,
439 {
440 SimpleExpr::Binary(
441 Box::new(self.into_simple_expr()),
442 BinOper::Mul,
443 Box::new(v.into()),
444 )
445 }
446
447 fn div<V>(self, v: V) -> SimpleExpr
449 where
450 V: Into<SimpleExpr>,
451 {
452 SimpleExpr::Binary(
453 Box::new(self.into_simple_expr()),
454 BinOper::Div,
455 Box::new(v.into()),
456 )
457 }
458
459 fn modulo<V>(self, v: V) -> SimpleExpr
461 where
462 V: Into<SimpleExpr>,
463 {
464 SimpleExpr::Binary(
465 Box::new(self.into_simple_expr()),
466 BinOper::Mod,
467 Box::new(v.into()),
468 )
469 }
470
471 fn bit_and<V>(self, v: V) -> SimpleExpr
477 where
478 V: Into<SimpleExpr>,
479 {
480 SimpleExpr::Binary(
481 Box::new(self.into_simple_expr()),
482 BinOper::BitAnd,
483 Box::new(v.into()),
484 )
485 }
486
487 fn bit_or<V>(self, v: V) -> SimpleExpr
489 where
490 V: Into<SimpleExpr>,
491 {
492 SimpleExpr::Binary(
493 Box::new(self.into_simple_expr()),
494 BinOper::BitOr,
495 Box::new(v.into()),
496 )
497 }
498
499 fn left_shift<V>(self, v: V) -> SimpleExpr
501 where
502 V: Into<SimpleExpr>,
503 {
504 SimpleExpr::Binary(
505 Box::new(self.into_simple_expr()),
506 BinOper::LShift,
507 Box::new(v.into()),
508 )
509 }
510
511 fn right_shift<V>(self, v: V) -> SimpleExpr
513 where
514 V: Into<SimpleExpr>,
515 {
516 SimpleExpr::Binary(
517 Box::new(self.into_simple_expr()),
518 BinOper::RShift,
519 Box::new(v.into()),
520 )
521 }
522
523 fn cast_as<T>(self, type_name: T) -> SimpleExpr
536 where
537 T: crate::types::IntoIden,
538 {
539 SimpleExpr::Cast(Box::new(self.into_simple_expr()), type_name.into_iden())
540 }
541
542 fn as_enum<T>(self, type_name: T) -> SimpleExpr
544 where
545 T: crate::types::IntoIden,
546 {
547 SimpleExpr::AsEnum(type_name.into_iden(), Box::new(self.into_simple_expr()))
548 }
549}
550
551impl ExprTrait for SimpleExpr {
553 fn into_simple_expr(self) -> SimpleExpr {
554 self
555 }
556}
557
558impl ExprTrait for super::expr::Expr {
560 fn into_simple_expr(self) -> SimpleExpr {
561 self.into_simple_expr()
562 }
563}
564
565#[cfg(test)]
566mod tests {
567 use super::*;
568 use crate::expr::Expr;
569 use rstest::rstest;
570
571 #[rstest]
572 fn test_eq() {
573 let expr = Expr::col("name").eq("Alice");
574 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Equal, _)));
575 }
576
577 #[rstest]
578 fn test_ne() {
579 let expr = Expr::col("name").ne("Bob");
580 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotEqual, _)));
581 }
582
583 #[rstest]
584 fn test_lt() {
585 let expr = Expr::col("age").lt(18);
586 assert!(matches!(
587 expr,
588 SimpleExpr::Binary(_, BinOper::SmallerThan, _)
589 ));
590 }
591
592 #[rstest]
593 fn test_lte() {
594 let expr = Expr::col("age").lte(65);
595 assert!(matches!(
596 expr,
597 SimpleExpr::Binary(_, BinOper::SmallerThanOrEqual, _)
598 ));
599 }
600
601 #[rstest]
602 fn test_gt() {
603 let expr = Expr::col("age").gt(18);
604 assert!(matches!(
605 expr,
606 SimpleExpr::Binary(_, BinOper::GreaterThan, _)
607 ));
608 }
609
610 #[rstest]
611 fn test_gte() {
612 let expr = Expr::col("age").gte(18);
613 assert!(matches!(
614 expr,
615 SimpleExpr::Binary(_, BinOper::GreaterThanOrEqual, _)
616 ));
617 }
618
619 #[rstest]
620 fn test_is_null() {
621 let expr = Expr::col("deleted_at").is_null();
622 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Is, _)));
623 }
624
625 #[rstest]
626 fn test_is_not_null() {
627 let expr = Expr::col("name").is_not_null();
628 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::IsNot, _)));
629 }
630
631 #[rstest]
632 fn test_between() {
633 let expr = Expr::col("age").between(18, 65);
634 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Between, _)));
635 }
636
637 #[rstest]
638 fn test_not_between() {
639 let expr = Expr::col("age").not_between(0, 17);
640 assert!(matches!(
641 expr,
642 SimpleExpr::Binary(_, BinOper::NotBetween, _)
643 ));
644 }
645
646 #[rstest]
647 fn test_is_in() {
648 let expr = Expr::col("status").is_in(["active", "pending"]);
649 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::In, _)));
650 }
651
652 #[rstest]
653 fn test_is_not_in() {
654 let expr = Expr::col("status").is_not_in(["deleted", "banned"]);
655 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotIn, _)));
656 }
657
658 #[rstest]
659 fn test_like() {
660 let expr = Expr::col("name").like("%john%");
661 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Like, _)));
662 }
663
664 #[rstest]
665 fn test_not_like() {
666 let expr = Expr::col("name").not_like("%admin%");
667 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::NotLike, _)));
668 }
669
670 #[rstest]
671 fn test_starts_with() {
672 let expr = Expr::col("name").starts_with("John");
673 assert!(matches!(expr, SimpleExpr::CustomWithExpr(_, _)));
675 if let SimpleExpr::CustomWithExpr(template, _) = &expr {
676 assert_eq!(template, "? LIKE ? ESCAPE '\\'");
677 }
678 }
679
680 #[rstest]
681 fn test_ends_with() {
682 let expr = Expr::col("email").ends_with("@example.com");
683 assert!(matches!(expr, SimpleExpr::CustomWithExpr(_, _)));
685 if let SimpleExpr::CustomWithExpr(template, _) = &expr {
686 assert_eq!(template, "? LIKE ? ESCAPE '\\'");
687 }
688 }
689
690 #[rstest]
691 fn test_contains() {
692 let expr = Expr::col("description").contains("important");
693 assert!(matches!(expr, SimpleExpr::CustomWithExpr(_, _)));
695 if let SimpleExpr::CustomWithExpr(template, _) = &expr {
696 assert_eq!(template, "? LIKE ? ESCAPE '\\'");
697 }
698 }
699
700 #[rstest]
701 fn test_and() {
702 let expr = Expr::col("active")
703 .eq(true)
704 .and(Expr::col("verified").eq(true));
705 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::And, _)));
706 }
707
708 #[rstest]
709 fn test_or() {
710 let expr = Expr::col("role")
711 .eq("admin")
712 .or(Expr::col("role").eq("moderator"));
713 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Or, _)));
714 }
715
716 #[rstest]
717 fn test_not() {
718 let expr = Expr::col("deleted").not();
719 assert!(matches!(expr, SimpleExpr::Unary(UnOper::Not, _)));
720 }
721
722 #[rstest]
723 fn test_add() {
724 let expr = Expr::col("price").add(10);
725 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Add, _)));
726 }
727
728 #[rstest]
729 fn test_sub() {
730 let expr = Expr::col("quantity").sub(1);
731 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Sub, _)));
732 }
733
734 #[rstest]
735 fn test_mul() {
736 let expr = Expr::col("price").mul(Expr::col("quantity"));
737 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Mul, _)));
738 }
739
740 #[rstest]
741 fn test_div() {
742 let expr = Expr::col("total").div(Expr::col("count"));
743 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Div, _)));
744 }
745
746 #[rstest]
747 fn test_modulo() {
748 let expr = Expr::col("value").modulo(2);
749 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::Mod, _)));
750 }
751
752 #[rstest]
753 fn test_cast_as() {
754 let expr = Expr::col("age").cast_as("TEXT");
755 assert!(matches!(expr, SimpleExpr::Cast(_, _)));
756 }
757
758 #[rstest]
759 fn test_chained_operations() {
760 let expr = Expr::col("age")
762 .gte(18)
763 .and(Expr::col("active").eq(true))
764 .and(Expr::col("verified").is_not_null());
765
766 assert!(matches!(expr, SimpleExpr::Binary(_, BinOper::And, _)));
767 }
768}