1use serde::{Deserialize, Serialize};
65use smallvec::SmallVec;
66use std::borrow::Cow;
67use tracing::debug;
68
69pub type ValueList = Vec<FilterValue>;
84
85pub type SmallValueList = SmallVec<[FilterValue; 8]>;
88
89pub type LargeValueList = SmallVec<[FilterValue; 32]>;
92
93pub type FieldName = Cow<'static, str>;
114
115#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
148#[serde(untagged)]
149pub enum FilterValue {
150 Null,
152 Bool(bool),
154 Int(i64),
156 Float(f64),
158 String(String),
160 Json(serde_json::Value),
162 List(Vec<FilterValue>),
164}
165
166impl FilterValue {
167 pub fn is_null(&self) -> bool {
169 matches!(self, Self::Null)
170 }
171
172 pub fn to_sql_placeholder(&self, param_index: usize) -> String {
174 format!("${}", param_index)
175 }
176}
177
178impl From<bool> for FilterValue {
179 fn from(v: bool) -> Self {
180 Self::Bool(v)
181 }
182}
183
184impl From<i32> for FilterValue {
185 fn from(v: i32) -> Self {
186 Self::Int(v as i64)
187 }
188}
189
190impl From<i64> for FilterValue {
191 fn from(v: i64) -> Self {
192 Self::Int(v)
193 }
194}
195
196impl From<f64> for FilterValue {
197 fn from(v: f64) -> Self {
198 Self::Float(v)
199 }
200}
201
202impl From<String> for FilterValue {
203 fn from(v: String) -> Self {
204 Self::String(v)
205 }
206}
207
208impl From<&str> for FilterValue {
209 fn from(v: &str) -> Self {
210 Self::String(v.to_string())
211 }
212}
213
214impl<T: Into<FilterValue>> From<Vec<T>> for FilterValue {
215 fn from(v: Vec<T>) -> Self {
216 Self::List(v.into_iter().map(Into::into).collect())
217 }
218}
219
220impl<T: Into<FilterValue>> From<Option<T>> for FilterValue {
221 fn from(v: Option<T>) -> Self {
222 match v {
223 Some(v) => v.into(),
224 None => Self::Null,
225 }
226 }
227}
228
229#[derive(Debug, Clone, PartialEq)]
231pub enum ScalarFilter<T> {
232 Equals(T),
234 Not(Box<T>),
236 In(Vec<T>),
238 NotIn(Vec<T>),
240 Lt(T),
242 Lte(T),
244 Gt(T),
246 Gte(T),
248 Contains(T),
250 StartsWith(T),
252 EndsWith(T),
254 IsNull,
256 IsNotNull,
258}
259
260impl<T: Into<FilterValue>> ScalarFilter<T> {
261 pub fn into_filter(self, column: impl Into<FieldName>) -> Filter {
266 let column = column.into();
267 match self {
268 Self::Equals(v) => Filter::Equals(column, v.into()),
269 Self::Not(v) => Filter::NotEquals(column, (*v).into()),
270 Self::In(values) => Filter::In(column, values.into_iter().map(Into::into).collect()),
271 Self::NotIn(values) => {
272 Filter::NotIn(column, values.into_iter().map(Into::into).collect())
273 }
274 Self::Lt(v) => Filter::Lt(column, v.into()),
275 Self::Lte(v) => Filter::Lte(column, v.into()),
276 Self::Gt(v) => Filter::Gt(column, v.into()),
277 Self::Gte(v) => Filter::Gte(column, v.into()),
278 Self::Contains(v) => Filter::Contains(column, v.into()),
279 Self::StartsWith(v) => Filter::StartsWith(column, v.into()),
280 Self::EndsWith(v) => Filter::EndsWith(column, v.into()),
281 Self::IsNull => Filter::IsNull(column),
282 Self::IsNotNull => Filter::IsNotNull(column),
283 }
284 }
285}
286
287#[derive(Debug, Clone, PartialEq)]
308#[repr(C)] #[derive(Default)]
310pub enum Filter {
311 #[default]
313 None,
314
315 Equals(FieldName, FilterValue),
317 NotEquals(FieldName, FilterValue),
319
320 Lt(FieldName, FilterValue),
322 Lte(FieldName, FilterValue),
324 Gt(FieldName, FilterValue),
326 Gte(FieldName, FilterValue),
328
329 In(FieldName, ValueList),
331 NotIn(FieldName, ValueList),
333
334 Contains(FieldName, FilterValue),
336 StartsWith(FieldName, FilterValue),
338 EndsWith(FieldName, FilterValue),
340
341 IsNull(FieldName),
343 IsNotNull(FieldName),
345
346 And(Box<[Filter]>),
351 Or(Box<[Filter]>),
356 Not(Box<Filter>),
358}
359
360impl Filter {
361 #[inline(always)]
363 pub fn none() -> Self {
364 Self::None
365 }
366
367 #[inline(always)]
369 pub fn is_none(&self) -> bool {
370 matches!(self, Self::None)
371 }
372
373 #[inline]
379 pub fn and(filters: impl IntoIterator<Item = Filter>) -> Self {
380 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
381 let count = filters.len();
382 let result = match count {
383 0 => Self::None,
384 1 => filters.into_iter().next().unwrap(),
385 _ => Self::And(filters.into_boxed_slice()),
386 };
387 debug!(count, "Filter::and() created");
388 result
389 }
390
391 #[inline(always)]
395 pub fn and2(a: Filter, b: Filter) -> Self {
396 match (a.is_none(), b.is_none()) {
397 (true, true) => Self::None,
398 (true, false) => b,
399 (false, true) => a,
400 (false, false) => Self::And(Box::new([a, b])),
401 }
402 }
403
404 #[inline]
410 pub fn or(filters: impl IntoIterator<Item = Filter>) -> Self {
411 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
412 let count = filters.len();
413 let result = match count {
414 0 => Self::None,
415 1 => filters.into_iter().next().unwrap(),
416 _ => Self::Or(filters.into_boxed_slice()),
417 };
418 debug!(count, "Filter::or() created");
419 result
420 }
421
422 #[inline(always)]
426 pub fn or2(a: Filter, b: Filter) -> Self {
427 match (a.is_none(), b.is_none()) {
428 (true, true) => Self::None,
429 (true, false) => b,
430 (false, true) => a,
431 (false, false) => Self::Or(Box::new([a, b])),
432 }
433 }
434
435 #[inline(always)]
456 pub fn and_n<const N: usize>(filters: [Filter; N]) -> Self {
457 Self::And(Box::new(filters))
459 }
460
461 #[inline(always)]
466 pub fn or_n<const N: usize>(filters: [Filter; N]) -> Self {
467 Self::Or(Box::new(filters))
468 }
469
470 #[inline(always)]
472 pub fn and3(a: Filter, b: Filter, c: Filter) -> Self {
473 Self::And(Box::new([a, b, c]))
474 }
475
476 #[inline(always)]
478 pub fn and4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
479 Self::And(Box::new([a, b, c, d]))
480 }
481
482 #[inline(always)]
484 pub fn and5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
485 Self::And(Box::new([a, b, c, d, e]))
486 }
487
488 #[inline(always)]
490 pub fn or3(a: Filter, b: Filter, c: Filter) -> Self {
491 Self::Or(Box::new([a, b, c]))
492 }
493
494 #[inline(always)]
496 pub fn or4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
497 Self::Or(Box::new([a, b, c, d]))
498 }
499
500 #[inline(always)]
502 pub fn or5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
503 Self::Or(Box::new([a, b, c, d, e]))
504 }
505
506 #[inline]
515 pub fn in_i64(field: impl Into<FieldName>, values: impl IntoIterator<Item = i64>) -> Self {
516 let list: ValueList = values.into_iter().map(FilterValue::Int).collect();
517 Self::In(field.into(), list)
518 }
519
520 #[inline]
522 pub fn in_i32(field: impl Into<FieldName>, values: impl IntoIterator<Item = i32>) -> Self {
523 let list: ValueList = values
524 .into_iter()
525 .map(|v| FilterValue::Int(v as i64))
526 .collect();
527 Self::In(field.into(), list)
528 }
529
530 #[inline]
532 pub fn in_strings(
533 field: impl Into<FieldName>,
534 values: impl IntoIterator<Item = String>,
535 ) -> Self {
536 let list: ValueList = values.into_iter().map(FilterValue::String).collect();
537 Self::In(field.into(), list)
538 }
539
540 #[inline]
544 pub fn in_values(field: impl Into<FieldName>, values: ValueList) -> Self {
545 Self::In(field.into(), values)
546 }
547
548 #[inline]
552 pub fn in_range(field: impl Into<FieldName>, range: std::ops::Range<i64>) -> Self {
553 let list: ValueList = range.map(FilterValue::Int).collect();
554 Self::In(field.into(), list)
555 }
556
557 #[inline(always)]
562 pub fn in_i64_slice(field: impl Into<FieldName>, values: &[i64]) -> Self {
563 let mut list = Vec::with_capacity(values.len());
564 for &v in values {
565 list.push(FilterValue::Int(v));
566 }
567 Self::In(field.into(), list)
568 }
569
570 #[inline(always)]
572 pub fn in_i32_slice(field: impl Into<FieldName>, values: &[i32]) -> Self {
573 let mut list = Vec::with_capacity(values.len());
574 for &v in values {
575 list.push(FilterValue::Int(v as i64));
576 }
577 Self::In(field.into(), list)
578 }
579
580 #[inline(always)]
582 pub fn in_str_slice(field: impl Into<FieldName>, values: &[&str]) -> Self {
583 let mut list = Vec::with_capacity(values.len());
584 for &v in values {
585 list.push(FilterValue::String(v.to_string()));
586 }
587 Self::In(field.into(), list)
588 }
589
590 #[inline]
592 #[allow(clippy::should_implement_trait)]
593 pub fn not(filter: Filter) -> Self {
594 if filter.is_none() {
595 return Self::None;
596 }
597 Self::Not(Box::new(filter))
598 }
599
600 #[inline]
614 pub fn in_slice<T: Into<FilterValue> + Clone>(
615 field: impl Into<FieldName>,
616 values: &[T],
617 ) -> Self {
618 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
619 Self::In(field.into(), list)
620 }
621
622 #[inline]
633 pub fn not_in_slice<T: Into<FilterValue> + Clone>(
634 field: impl Into<FieldName>,
635 values: &[T],
636 ) -> Self {
637 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
638 Self::NotIn(field.into(), list)
639 }
640
641 #[inline]
653 pub fn in_array<T: Into<FilterValue>, const N: usize>(
654 field: impl Into<FieldName>,
655 values: [T; N],
656 ) -> Self {
657 let list: ValueList = values.into_iter().map(Into::into).collect();
658 Self::In(field.into(), list)
659 }
660
661 #[inline]
663 pub fn not_in_array<T: Into<FilterValue>, const N: usize>(
664 field: impl Into<FieldName>,
665 values: [T; N],
666 ) -> Self {
667 let list: ValueList = values.into_iter().map(Into::into).collect();
668 Self::NotIn(field.into(), list)
669 }
670
671 pub fn and_then(self, other: Filter) -> Self {
673 if self.is_none() {
674 return other;
675 }
676 if other.is_none() {
677 return self;
678 }
679 match self {
680 Self::And(filters) => {
681 let mut vec: Vec<_> = filters.into_vec();
683 vec.push(other);
684 Self::And(vec.into_boxed_slice())
685 }
686 _ => Self::And(Box::new([self, other])),
687 }
688 }
689
690 pub fn or_else(self, other: Filter) -> Self {
692 if self.is_none() {
693 return other;
694 }
695 if other.is_none() {
696 return self;
697 }
698 match self {
699 Self::Or(filters) => {
700 let mut vec: Vec<_> = filters.into_vec();
702 vec.push(other);
703 Self::Or(vec.into_boxed_slice())
704 }
705 _ => Self::Or(Box::new([self, other])),
706 }
707 }
708
709 pub fn to_sql(&self, param_offset: usize) -> (String, Vec<FilterValue>) {
712 let mut params = Vec::new();
713 let sql = self.to_sql_with_params(param_offset, &mut params);
714 (sql, params)
715 }
716
717 fn to_sql_with_params(&self, mut param_idx: usize, params: &mut Vec<FilterValue>) -> String {
718 match self {
719 Self::None => "TRUE".to_string(),
720
721 Self::Equals(col, val) => {
722 if val.is_null() {
723 format!("{} IS NULL", col)
724 } else {
725 params.push(val.clone());
726 param_idx += params.len();
727 format!("{} = ${}", col, param_idx)
728 }
729 }
730 Self::NotEquals(col, val) => {
731 if val.is_null() {
732 format!("{} IS NOT NULL", col)
733 } else {
734 params.push(val.clone());
735 param_idx += params.len();
736 format!("{} != ${}", col, param_idx)
737 }
738 }
739
740 Self::Lt(col, val) => {
741 params.push(val.clone());
742 param_idx += params.len();
743 format!("{} < ${}", col, param_idx)
744 }
745 Self::Lte(col, val) => {
746 params.push(val.clone());
747 param_idx += params.len();
748 format!("{} <= ${}", col, param_idx)
749 }
750 Self::Gt(col, val) => {
751 params.push(val.clone());
752 param_idx += params.len();
753 format!("{} > ${}", col, param_idx)
754 }
755 Self::Gte(col, val) => {
756 params.push(val.clone());
757 param_idx += params.len();
758 format!("{} >= ${}", col, param_idx)
759 }
760
761 Self::In(col, values) => {
762 if values.is_empty() {
763 return "FALSE".to_string();
764 }
765 let placeholders: Vec<_> = values
766 .iter()
767 .map(|v| {
768 params.push(v.clone());
769 param_idx += params.len();
770 format!("${}", param_idx)
771 })
772 .collect();
773 format!("{} IN ({})", col, placeholders.join(", "))
774 }
775 Self::NotIn(col, values) => {
776 if values.is_empty() {
777 return "TRUE".to_string();
778 }
779 let placeholders: Vec<_> = values
780 .iter()
781 .map(|v| {
782 params.push(v.clone());
783 param_idx += params.len();
784 format!("${}", param_idx)
785 })
786 .collect();
787 format!("{} NOT IN ({})", col, placeholders.join(", "))
788 }
789
790 Self::Contains(col, val) => {
791 if let FilterValue::String(s) = val {
792 params.push(FilterValue::String(format!("%{}%", s)));
793 } else {
794 params.push(val.clone());
795 }
796 param_idx += params.len();
797 format!("{} LIKE ${}", col, param_idx)
798 }
799 Self::StartsWith(col, val) => {
800 if let FilterValue::String(s) = val {
801 params.push(FilterValue::String(format!("{}%", s)));
802 } else {
803 params.push(val.clone());
804 }
805 param_idx += params.len();
806 format!("{} LIKE ${}", col, param_idx)
807 }
808 Self::EndsWith(col, val) => {
809 if let FilterValue::String(s) = val {
810 params.push(FilterValue::String(format!("%{}", s)));
811 } else {
812 params.push(val.clone());
813 }
814 param_idx += params.len();
815 format!("{} LIKE ${}", col, param_idx)
816 }
817
818 Self::IsNull(col) => format!("{} IS NULL", col),
819 Self::IsNotNull(col) => format!("{} IS NOT NULL", col),
820
821 Self::And(filters) => {
822 if filters.is_empty() {
823 return "TRUE".to_string();
824 }
825 let parts: Vec<_> = filters
826 .iter()
827 .map(|f| f.to_sql_with_params(param_idx + params.len(), params))
828 .collect();
829 format!("({})", parts.join(" AND "))
830 }
831 Self::Or(filters) => {
832 if filters.is_empty() {
833 return "FALSE".to_string();
834 }
835 let parts: Vec<_> = filters
836 .iter()
837 .map(|f| f.to_sql_with_params(param_idx + params.len(), params))
838 .collect();
839 format!("({})", parts.join(" OR "))
840 }
841 Self::Not(filter) => {
842 let inner = filter.to_sql_with_params(param_idx, params);
843 format!("NOT ({})", inner)
844 }
845 }
846 }
847
848 #[inline]
866 pub fn and_builder(capacity: usize) -> AndFilterBuilder {
867 AndFilterBuilder::with_capacity(capacity)
868 }
869
870 #[inline]
887 pub fn or_builder(capacity: usize) -> OrFilterBuilder {
888 OrFilterBuilder::with_capacity(capacity)
889 }
890
891 #[inline]
907 pub fn builder() -> FluentFilterBuilder {
908 FluentFilterBuilder::new()
909 }
910}
911
912#[derive(Debug, Clone)]
916pub struct AndFilterBuilder {
917 filters: Vec<Filter>,
918}
919
920impl AndFilterBuilder {
921 #[inline]
923 pub fn new() -> Self {
924 Self {
925 filters: Vec::new(),
926 }
927 }
928
929 #[inline]
931 pub fn with_capacity(capacity: usize) -> Self {
932 Self {
933 filters: Vec::with_capacity(capacity),
934 }
935 }
936
937 #[inline]
939 pub fn push(mut self, filter: Filter) -> Self {
940 if !filter.is_none() {
941 self.filters.push(filter);
942 }
943 self
944 }
945
946 #[inline]
948 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
949 self.filters
950 .extend(filters.into_iter().filter(|f| !f.is_none()));
951 self
952 }
953
954 #[inline]
956 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
957 if condition { self.push(filter) } else { self }
958 }
959
960 #[inline]
962 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
963 where
964 F: Into<Filter>,
965 {
966 match opt {
967 Some(f) => self.push(f.into()),
968 None => self,
969 }
970 }
971
972 #[inline]
974 pub fn build(self) -> Filter {
975 match self.filters.len() {
976 0 => Filter::None,
977 1 => self.filters.into_iter().next().unwrap(),
978 _ => Filter::And(self.filters.into_boxed_slice()),
979 }
980 }
981
982 #[inline]
984 pub fn len(&self) -> usize {
985 self.filters.len()
986 }
987
988 #[inline]
990 pub fn is_empty(&self) -> bool {
991 self.filters.is_empty()
992 }
993}
994
995impl Default for AndFilterBuilder {
996 fn default() -> Self {
997 Self::new()
998 }
999}
1000
1001#[derive(Debug, Clone)]
1005pub struct OrFilterBuilder {
1006 filters: Vec<Filter>,
1007}
1008
1009impl OrFilterBuilder {
1010 #[inline]
1012 pub fn new() -> Self {
1013 Self {
1014 filters: Vec::new(),
1015 }
1016 }
1017
1018 #[inline]
1020 pub fn with_capacity(capacity: usize) -> Self {
1021 Self {
1022 filters: Vec::with_capacity(capacity),
1023 }
1024 }
1025
1026 #[inline]
1028 pub fn push(mut self, filter: Filter) -> Self {
1029 if !filter.is_none() {
1030 self.filters.push(filter);
1031 }
1032 self
1033 }
1034
1035 #[inline]
1037 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1038 self.filters
1039 .extend(filters.into_iter().filter(|f| !f.is_none()));
1040 self
1041 }
1042
1043 #[inline]
1045 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1046 if condition { self.push(filter) } else { self }
1047 }
1048
1049 #[inline]
1051 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1052 where
1053 F: Into<Filter>,
1054 {
1055 match opt {
1056 Some(f) => self.push(f.into()),
1057 None => self,
1058 }
1059 }
1060
1061 #[inline]
1063 pub fn build(self) -> Filter {
1064 match self.filters.len() {
1065 0 => Filter::None,
1066 1 => self.filters.into_iter().next().unwrap(),
1067 _ => Filter::Or(self.filters.into_boxed_slice()),
1068 }
1069 }
1070
1071 #[inline]
1073 pub fn len(&self) -> usize {
1074 self.filters.len()
1075 }
1076
1077 #[inline]
1079 pub fn is_empty(&self) -> bool {
1080 self.filters.is_empty()
1081 }
1082}
1083
1084impl Default for OrFilterBuilder {
1085 fn default() -> Self {
1086 Self::new()
1087 }
1088}
1089
1090#[derive(Debug, Clone)]
1115pub struct FluentFilterBuilder {
1116 filters: Vec<Filter>,
1117}
1118
1119impl FluentFilterBuilder {
1120 #[inline]
1122 pub fn new() -> Self {
1123 Self {
1124 filters: Vec::new(),
1125 }
1126 }
1127
1128 #[inline]
1130 pub fn with_capacity(mut self, capacity: usize) -> Self {
1131 self.filters.reserve(capacity);
1132 self
1133 }
1134
1135 #[inline]
1137 pub fn eq<F, V>(mut self, field: F, value: V) -> Self
1138 where
1139 F: Into<FieldName>,
1140 V: Into<FilterValue>,
1141 {
1142 self.filters
1143 .push(Filter::Equals(field.into(), value.into()));
1144 self
1145 }
1146
1147 #[inline]
1149 pub fn ne<F, V>(mut self, field: F, value: V) -> Self
1150 where
1151 F: Into<FieldName>,
1152 V: Into<FilterValue>,
1153 {
1154 self.filters
1155 .push(Filter::NotEquals(field.into(), value.into()));
1156 self
1157 }
1158
1159 #[inline]
1161 pub fn lt<F, V>(mut self, field: F, value: V) -> Self
1162 where
1163 F: Into<FieldName>,
1164 V: Into<FilterValue>,
1165 {
1166 self.filters.push(Filter::Lt(field.into(), value.into()));
1167 self
1168 }
1169
1170 #[inline]
1172 pub fn lte<F, V>(mut self, field: F, value: V) -> Self
1173 where
1174 F: Into<FieldName>,
1175 V: Into<FilterValue>,
1176 {
1177 self.filters.push(Filter::Lte(field.into(), value.into()));
1178 self
1179 }
1180
1181 #[inline]
1183 pub fn gt<F, V>(mut self, field: F, value: V) -> Self
1184 where
1185 F: Into<FieldName>,
1186 V: Into<FilterValue>,
1187 {
1188 self.filters.push(Filter::Gt(field.into(), value.into()));
1189 self
1190 }
1191
1192 #[inline]
1194 pub fn gte<F, V>(mut self, field: F, value: V) -> Self
1195 where
1196 F: Into<FieldName>,
1197 V: Into<FilterValue>,
1198 {
1199 self.filters.push(Filter::Gte(field.into(), value.into()));
1200 self
1201 }
1202
1203 #[inline]
1205 pub fn is_in<F, I, V>(mut self, field: F, values: I) -> Self
1206 where
1207 F: Into<FieldName>,
1208 I: IntoIterator<Item = V>,
1209 V: Into<FilterValue>,
1210 {
1211 self.filters.push(Filter::In(
1212 field.into(),
1213 values.into_iter().map(Into::into).collect(),
1214 ));
1215 self
1216 }
1217
1218 #[inline]
1220 pub fn not_in<F, I, V>(mut self, field: F, values: I) -> Self
1221 where
1222 F: Into<FieldName>,
1223 I: IntoIterator<Item = V>,
1224 V: Into<FilterValue>,
1225 {
1226 self.filters.push(Filter::NotIn(
1227 field.into(),
1228 values.into_iter().map(Into::into).collect(),
1229 ));
1230 self
1231 }
1232
1233 #[inline]
1235 pub fn contains<F, V>(mut self, field: F, value: V) -> Self
1236 where
1237 F: Into<FieldName>,
1238 V: Into<FilterValue>,
1239 {
1240 self.filters
1241 .push(Filter::Contains(field.into(), value.into()));
1242 self
1243 }
1244
1245 #[inline]
1247 pub fn starts_with<F, V>(mut self, field: F, value: V) -> Self
1248 where
1249 F: Into<FieldName>,
1250 V: Into<FilterValue>,
1251 {
1252 self.filters
1253 .push(Filter::StartsWith(field.into(), value.into()));
1254 self
1255 }
1256
1257 #[inline]
1259 pub fn ends_with<F, V>(mut self, field: F, value: V) -> Self
1260 where
1261 F: Into<FieldName>,
1262 V: Into<FilterValue>,
1263 {
1264 self.filters
1265 .push(Filter::EndsWith(field.into(), value.into()));
1266 self
1267 }
1268
1269 #[inline]
1271 pub fn is_null<F>(mut self, field: F) -> Self
1272 where
1273 F: Into<FieldName>,
1274 {
1275 self.filters.push(Filter::IsNull(field.into()));
1276 self
1277 }
1278
1279 #[inline]
1281 pub fn is_not_null<F>(mut self, field: F) -> Self
1282 where
1283 F: Into<FieldName>,
1284 {
1285 self.filters.push(Filter::IsNotNull(field.into()));
1286 self
1287 }
1288
1289 #[inline]
1291 pub fn filter(mut self, filter: Filter) -> Self {
1292 if !filter.is_none() {
1293 self.filters.push(filter);
1294 }
1295 self
1296 }
1297
1298 #[inline]
1300 pub fn filter_if(self, condition: bool, filter: Filter) -> Self {
1301 if condition { self.filter(filter) } else { self }
1302 }
1303
1304 #[inline]
1306 pub fn filter_if_some<F>(self, opt: Option<F>) -> Self
1307 where
1308 F: Into<Filter>,
1309 {
1310 match opt {
1311 Some(f) => self.filter(f.into()),
1312 None => self,
1313 }
1314 }
1315
1316 #[inline]
1318 pub fn build_and(self) -> Filter {
1319 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1320 match filters.len() {
1321 0 => Filter::None,
1322 1 => filters.into_iter().next().unwrap(),
1323 _ => Filter::And(filters.into_boxed_slice()),
1324 }
1325 }
1326
1327 #[inline]
1329 pub fn build_or(self) -> Filter {
1330 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1331 match filters.len() {
1332 0 => Filter::None,
1333 1 => filters.into_iter().next().unwrap(),
1334 _ => Filter::Or(filters.into_boxed_slice()),
1335 }
1336 }
1337
1338 #[inline]
1340 pub fn len(&self) -> usize {
1341 self.filters.len()
1342 }
1343
1344 #[inline]
1346 pub fn is_empty(&self) -> bool {
1347 self.filters.is_empty()
1348 }
1349}
1350
1351impl Default for FluentFilterBuilder {
1352 fn default() -> Self {
1353 Self::new()
1354 }
1355}
1356
1357
1358#[cfg(test)]
1359mod tests {
1360 use super::*;
1361
1362 #[test]
1363 fn test_filter_value_from() {
1364 assert_eq!(FilterValue::from(42i32), FilterValue::Int(42));
1365 assert_eq!(
1366 FilterValue::from("hello"),
1367 FilterValue::String("hello".to_string())
1368 );
1369 assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
1370 }
1371
1372 #[test]
1373 fn test_scalar_filter_equals() {
1374 let filter = ScalarFilter::Equals("test@example.com".to_string()).into_filter("email");
1375
1376 let (sql, params) = filter.to_sql(0);
1377 assert_eq!(sql, "email = $1");
1378 assert_eq!(params.len(), 1);
1379 }
1380
1381 #[test]
1382 fn test_filter_and() {
1383 let f1 = Filter::Equals("name".into(), "Alice".into());
1384 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1385 let combined = Filter::and([f1, f2]);
1386
1387 let (sql, params) = combined.to_sql(0);
1388 assert!(sql.contains("AND"));
1389 assert_eq!(params.len(), 2);
1390 }
1391
1392 #[test]
1393 fn test_filter_or() {
1394 let f1 = Filter::Equals("status".into(), "active".into());
1395 let f2 = Filter::Equals("status".into(), "pending".into());
1396 let combined = Filter::or([f1, f2]);
1397
1398 let (sql, _) = combined.to_sql(0);
1399 assert!(sql.contains("OR"));
1400 }
1401
1402 #[test]
1403 fn test_filter_not() {
1404 let filter = Filter::not(Filter::Equals("deleted".into(), FilterValue::Bool(true)));
1405
1406 let (sql, _) = filter.to_sql(0);
1407 assert!(sql.contains("NOT"));
1408 }
1409
1410 #[test]
1411 fn test_filter_is_null() {
1412 let filter = Filter::IsNull("deleted_at".into());
1413 let (sql, params) = filter.to_sql(0);
1414 assert_eq!(sql, "deleted_at IS NULL");
1415 assert!(params.is_empty());
1416 }
1417
1418 #[test]
1419 fn test_filter_in() {
1420 let filter = Filter::In("status".into(), vec!["active".into(), "pending".into()]);
1421 let (sql, params) = filter.to_sql(0);
1422 assert!(sql.contains("IN"));
1423 assert_eq!(params.len(), 2);
1424 }
1425
1426 #[test]
1427 fn test_filter_contains() {
1428 let filter = Filter::Contains("email".into(), "example".into());
1429 let (sql, params) = filter.to_sql(0);
1430 assert!(sql.contains("LIKE"));
1431 assert_eq!(params.len(), 1);
1432 if let FilterValue::String(s) = ¶ms[0] {
1433 assert!(s.contains("%example%"));
1434 }
1435 }
1436
1437 #[test]
1440 fn test_filter_value_is_null() {
1441 assert!(FilterValue::Null.is_null());
1442 assert!(!FilterValue::Bool(false).is_null());
1443 assert!(!FilterValue::Int(0).is_null());
1444 assert!(!FilterValue::Float(0.0).is_null());
1445 assert!(!FilterValue::String("".to_string()).is_null());
1446 }
1447
1448 #[test]
1449 fn test_filter_value_to_sql_placeholder() {
1450 let val = FilterValue::Int(42);
1451 assert_eq!(val.to_sql_placeholder(1), "$1");
1452 assert_eq!(val.to_sql_placeholder(10), "$10");
1453 }
1454
1455 #[test]
1456 fn test_filter_value_from_i64() {
1457 assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
1458 assert_eq!(FilterValue::from(-100i64), FilterValue::Int(-100));
1459 }
1460
1461 #[test]
1462 fn test_filter_value_from_f64() {
1463 assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1464 }
1465
1466 #[test]
1467 fn test_filter_value_from_string() {
1468 assert_eq!(
1469 FilterValue::from("hello".to_string()),
1470 FilterValue::String("hello".to_string())
1471 );
1472 }
1473
1474 #[test]
1475 fn test_filter_value_from_vec() {
1476 let values: Vec<i32> = vec![1, 2, 3];
1477 let filter_val: FilterValue = values.into();
1478 if let FilterValue::List(list) = filter_val {
1479 assert_eq!(list.len(), 3);
1480 assert_eq!(list[0], FilterValue::Int(1));
1481 assert_eq!(list[1], FilterValue::Int(2));
1482 assert_eq!(list[2], FilterValue::Int(3));
1483 } else {
1484 panic!("Expected List");
1485 }
1486 }
1487
1488 #[test]
1489 fn test_filter_value_from_option_some() {
1490 let val: FilterValue = Some(42i32).into();
1491 assert_eq!(val, FilterValue::Int(42));
1492 }
1493
1494 #[test]
1495 fn test_filter_value_from_option_none() {
1496 let val: FilterValue = Option::<i32>::None.into();
1497 assert_eq!(val, FilterValue::Null);
1498 }
1499
1500 #[test]
1503 fn test_scalar_filter_not() {
1504 let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1505 let (sql, params) = filter.to_sql(0);
1506 assert_eq!(sql, "name != $1");
1507 assert_eq!(params.len(), 1);
1508 }
1509
1510 #[test]
1511 fn test_scalar_filter_in() {
1512 let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1513 let (sql, params) = filter.to_sql(0);
1514 assert!(sql.contains("IN"));
1515 assert_eq!(params.len(), 2);
1516 }
1517
1518 #[test]
1519 fn test_scalar_filter_not_in() {
1520 let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1521 let (sql, params) = filter.to_sql(0);
1522 assert!(sql.contains("NOT IN"));
1523 assert_eq!(params.len(), 1);
1524 }
1525
1526 #[test]
1527 fn test_scalar_filter_lt() {
1528 let filter = ScalarFilter::Lt(100i32).into_filter("price");
1529 let (sql, params) = filter.to_sql(0);
1530 assert_eq!(sql, "price < $1");
1531 assert_eq!(params.len(), 1);
1532 }
1533
1534 #[test]
1535 fn test_scalar_filter_lte() {
1536 let filter = ScalarFilter::Lte(100i32).into_filter("price");
1537 let (sql, params) = filter.to_sql(0);
1538 assert_eq!(sql, "price <= $1");
1539 assert_eq!(params.len(), 1);
1540 }
1541
1542 #[test]
1543 fn test_scalar_filter_gt() {
1544 let filter = ScalarFilter::Gt(0i32).into_filter("quantity");
1545 let (sql, params) = filter.to_sql(0);
1546 assert_eq!(sql, "quantity > $1");
1547 assert_eq!(params.len(), 1);
1548 }
1549
1550 #[test]
1551 fn test_scalar_filter_gte() {
1552 let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1553 let (sql, params) = filter.to_sql(0);
1554 assert_eq!(sql, "quantity >= $1");
1555 assert_eq!(params.len(), 1);
1556 }
1557
1558 #[test]
1559 fn test_scalar_filter_starts_with() {
1560 let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1561 let (sql, params) = filter.to_sql(0);
1562 assert!(sql.contains("LIKE"));
1563 assert_eq!(params.len(), 1);
1564 if let FilterValue::String(s) = ¶ms[0] {
1565 assert!(s.starts_with("prefix"));
1566 assert!(s.ends_with("%"));
1567 }
1568 }
1569
1570 #[test]
1571 fn test_scalar_filter_ends_with() {
1572 let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
1573 let (sql, params) = filter.to_sql(0);
1574 assert!(sql.contains("LIKE"));
1575 assert_eq!(params.len(), 1);
1576 if let FilterValue::String(s) = ¶ms[0] {
1577 assert!(s.starts_with("%"));
1578 assert!(s.ends_with("suffix"));
1579 }
1580 }
1581
1582 #[test]
1583 fn test_scalar_filter_is_null() {
1584 let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
1585 let (sql, params) = filter.to_sql(0);
1586 assert_eq!(sql, "deleted_at IS NULL");
1587 assert!(params.is_empty());
1588 }
1589
1590 #[test]
1591 fn test_scalar_filter_is_not_null() {
1592 let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
1593 let (sql, params) = filter.to_sql(0);
1594 assert_eq!(sql, "name IS NOT NULL");
1595 assert!(params.is_empty());
1596 }
1597
1598 #[test]
1601 fn test_filter_none() {
1602 let filter = Filter::none();
1603 assert!(filter.is_none());
1604 let (sql, params) = filter.to_sql(0);
1605 assert_eq!(sql, "TRUE"); assert!(params.is_empty());
1607 }
1608
1609 #[test]
1610 fn test_filter_not_equals() {
1611 let filter = Filter::NotEquals("status".into(), "deleted".into());
1612 let (sql, params) = filter.to_sql(0);
1613 assert_eq!(sql, "status != $1");
1614 assert_eq!(params.len(), 1);
1615 }
1616
1617 #[test]
1618 fn test_filter_lte() {
1619 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
1620 let (sql, params) = filter.to_sql(0);
1621 assert_eq!(sql, "price <= $1");
1622 assert_eq!(params.len(), 1);
1623 }
1624
1625 #[test]
1626 fn test_filter_gte() {
1627 let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
1628 let (sql, params) = filter.to_sql(0);
1629 assert_eq!(sql, "quantity >= $1");
1630 assert_eq!(params.len(), 1);
1631 }
1632
1633 #[test]
1634 fn test_filter_not_in() {
1635 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
1636 let (sql, params) = filter.to_sql(0);
1637 assert!(sql.contains("NOT IN"));
1638 assert_eq!(params.len(), 2);
1639 }
1640
1641 #[test]
1642 fn test_filter_starts_with() {
1643 let filter = Filter::StartsWith("email".into(), "admin".into());
1644 let (sql, params) = filter.to_sql(0);
1645 assert!(sql.contains("LIKE"));
1646 assert_eq!(params.len(), 1);
1647 }
1648
1649 #[test]
1650 fn test_filter_ends_with() {
1651 let filter = Filter::EndsWith("email".into(), "@example.com".into());
1652 let (sql, params) = filter.to_sql(0);
1653 assert!(sql.contains("LIKE"));
1654 assert_eq!(params.len(), 1);
1655 }
1656
1657 #[test]
1658 fn test_filter_is_not_null() {
1659 let filter = Filter::IsNotNull("name".into());
1660 let (sql, params) = filter.to_sql(0);
1661 assert_eq!(sql, "name IS NOT NULL");
1662 assert!(params.is_empty());
1663 }
1664
1665 #[test]
1668 fn test_filter_and_empty() {
1669 let filter = Filter::and([]);
1670 assert!(filter.is_none());
1671 }
1672
1673 #[test]
1674 fn test_filter_and_single() {
1675 let f = Filter::Equals("name".into(), "Alice".into());
1676 let combined = Filter::and([f.clone()]);
1677 assert_eq!(combined, f);
1678 }
1679
1680 #[test]
1681 fn test_filter_and_with_none() {
1682 let f1 = Filter::Equals("name".into(), "Alice".into());
1683 let f2 = Filter::None;
1684 let combined = Filter::and([f1.clone(), f2]);
1685 assert_eq!(combined, f1);
1686 }
1687
1688 #[test]
1689 fn test_filter_or_empty() {
1690 let filter = Filter::or([]);
1691 assert!(filter.is_none());
1692 }
1693
1694 #[test]
1695 fn test_filter_or_single() {
1696 let f = Filter::Equals("status".into(), "active".into());
1697 let combined = Filter::or([f.clone()]);
1698 assert_eq!(combined, f);
1699 }
1700
1701 #[test]
1702 fn test_filter_or_with_none() {
1703 let f1 = Filter::Equals("status".into(), "active".into());
1704 let f2 = Filter::None;
1705 let combined = Filter::or([f1.clone(), f2]);
1706 assert_eq!(combined, f1);
1707 }
1708
1709 #[test]
1710 fn test_filter_not_none() {
1711 let filter = Filter::not(Filter::None);
1712 assert!(filter.is_none());
1713 }
1714
1715 #[test]
1716 fn test_filter_and_then() {
1717 let f1 = Filter::Equals("name".into(), "Alice".into());
1718 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1719 let combined = f1.and_then(f2);
1720
1721 let (sql, params) = combined.to_sql(0);
1722 assert!(sql.contains("AND"));
1723 assert_eq!(params.len(), 2);
1724 }
1725
1726 #[test]
1727 fn test_filter_and_then_with_none_first() {
1728 let f1 = Filter::None;
1729 let f2 = Filter::Equals("name".into(), "Bob".into());
1730 let combined = f1.and_then(f2.clone());
1731 assert_eq!(combined, f2);
1732 }
1733
1734 #[test]
1735 fn test_filter_and_then_with_none_second() {
1736 let f1 = Filter::Equals("name".into(), "Alice".into());
1737 let f2 = Filter::None;
1738 let combined = f1.clone().and_then(f2);
1739 assert_eq!(combined, f1);
1740 }
1741
1742 #[test]
1743 fn test_filter_and_then_chained() {
1744 let f1 = Filter::Equals("a".into(), "1".into());
1745 let f2 = Filter::Equals("b".into(), "2".into());
1746 let f3 = Filter::Equals("c".into(), "3".into());
1747 let combined = f1.and_then(f2).and_then(f3);
1748
1749 let (sql, params) = combined.to_sql(0);
1750 assert!(sql.contains("AND"));
1751 assert_eq!(params.len(), 3);
1752 }
1753
1754 #[test]
1755 fn test_filter_or_else() {
1756 let f1 = Filter::Equals("status".into(), "active".into());
1757 let f2 = Filter::Equals("status".into(), "pending".into());
1758 let combined = f1.or_else(f2);
1759
1760 let (sql, _) = combined.to_sql(0);
1761 assert!(sql.contains("OR"));
1762 }
1763
1764 #[test]
1765 fn test_filter_or_else_with_none_first() {
1766 let f1 = Filter::None;
1767 let f2 = Filter::Equals("name".into(), "Bob".into());
1768 let combined = f1.or_else(f2.clone());
1769 assert_eq!(combined, f2);
1770 }
1771
1772 #[test]
1773 fn test_filter_or_else_with_none_second() {
1774 let f1 = Filter::Equals("name".into(), "Alice".into());
1775 let f2 = Filter::None;
1776 let combined = f1.clone().or_else(f2);
1777 assert_eq!(combined, f1);
1778 }
1779
1780 #[test]
1783 fn test_filter_nested_and_or() {
1784 let f1 = Filter::Equals("status".into(), "active".into());
1785 let f2 = Filter::and([
1786 Filter::Gt("age".into(), FilterValue::Int(18)),
1787 Filter::Lt("age".into(), FilterValue::Int(65)),
1788 ]);
1789 let combined = Filter::and([f1, f2]);
1790
1791 let (sql, params) = combined.to_sql(0);
1792 assert!(sql.contains("AND"));
1793 assert_eq!(params.len(), 3);
1794 }
1795
1796 #[test]
1797 fn test_filter_nested_not() {
1798 let inner = Filter::and([
1799 Filter::Equals("status".into(), "deleted".into()),
1800 Filter::Equals("archived".into(), FilterValue::Bool(true)),
1801 ]);
1802 let filter = Filter::not(inner);
1803
1804 let (sql, params) = filter.to_sql(0);
1805 assert!(sql.contains("NOT"));
1806 assert!(sql.contains("AND"));
1807 assert_eq!(params.len(), 2);
1808 }
1809
1810 #[test]
1811 fn test_filter_with_json_value() {
1812 let json_val = serde_json::json!({"key": "value"});
1813 let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
1814 let (sql, params) = filter.to_sql(0);
1815 assert_eq!(sql, "metadata = $1");
1816 assert_eq!(params.len(), 1);
1817 }
1818
1819 #[test]
1820 fn test_filter_in_empty_list() {
1821 let filter = Filter::In("status".into(), vec![]);
1822 let (sql, params) = filter.to_sql(0);
1823 assert!(
1825 sql.contains("FALSE")
1826 || sql.contains("1=0")
1827 || sql.is_empty()
1828 || sql.contains("status")
1829 );
1830 assert!(params.is_empty());
1831 }
1832
1833 #[test]
1834 fn test_filter_with_null_value() {
1835 let filter = Filter::IsNull("deleted_at".into());
1837 let (sql, params) = filter.to_sql(0);
1838 assert!(sql.contains("deleted_at"));
1839 assert!(sql.contains("IS NULL"));
1840 assert!(params.is_empty());
1841 }
1842
1843 #[test]
1846 fn test_and_builder_basic() {
1847 let filter = Filter::and_builder(3)
1848 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
1849 .push(Filter::Gt("score".into(), FilterValue::Int(100)))
1850 .push(Filter::IsNotNull("email".into()))
1851 .build();
1852
1853 let (sql, params) = filter.to_sql(0);
1854 assert!(sql.contains("AND"));
1855 assert_eq!(params.len(), 2); }
1857
1858 #[test]
1859 fn test_and_builder_empty() {
1860 let filter = Filter::and_builder(0).build();
1861 assert!(filter.is_none());
1862 }
1863
1864 #[test]
1865 fn test_and_builder_single() {
1866 let filter = Filter::and_builder(1)
1867 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
1868 .build();
1869
1870 assert!(matches!(filter, Filter::Equals(_, _)));
1872 }
1873
1874 #[test]
1875 fn test_and_builder_filters_none() {
1876 let filter = Filter::and_builder(3)
1877 .push(Filter::None)
1878 .push(Filter::Equals("id".into(), FilterValue::Int(1)))
1879 .push(Filter::None)
1880 .build();
1881
1882 assert!(matches!(filter, Filter::Equals(_, _)));
1884 }
1885
1886 #[test]
1887 fn test_and_builder_push_if() {
1888 let include_deleted = false;
1889 let filter = Filter::and_builder(2)
1890 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
1891 .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
1892 .build();
1893
1894 assert!(matches!(filter, Filter::Equals(_, _)));
1896 }
1897
1898 #[test]
1899 fn test_or_builder_basic() {
1900 let filter = Filter::or_builder(2)
1901 .push(Filter::Equals(
1902 "role".into(),
1903 FilterValue::String("admin".into()),
1904 ))
1905 .push(Filter::Equals(
1906 "role".into(),
1907 FilterValue::String("moderator".into()),
1908 ))
1909 .build();
1910
1911 let (sql, _) = filter.to_sql(0);
1912 assert!(sql.contains("OR"));
1913 }
1914
1915 #[test]
1916 fn test_or_builder_empty() {
1917 let filter = Filter::or_builder(0).build();
1918 assert!(filter.is_none());
1919 }
1920
1921 #[test]
1922 fn test_or_builder_single() {
1923 let filter = Filter::or_builder(1)
1924 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
1925 .build();
1926
1927 assert!(matches!(filter, Filter::Equals(_, _)));
1929 }
1930
1931 #[test]
1932 fn test_fluent_builder_and() {
1933 let filter = Filter::builder()
1934 .eq("status", "active")
1935 .gt("age", 18)
1936 .is_not_null("email")
1937 .build_and();
1938
1939 let (sql, params) = filter.to_sql(0);
1940 assert!(sql.contains("AND"));
1941 assert_eq!(params.len(), 2);
1942 }
1943
1944 #[test]
1945 fn test_fluent_builder_or() {
1946 let filter = Filter::builder()
1947 .eq("role", "admin")
1948 .eq("role", "moderator")
1949 .build_or();
1950
1951 let (sql, _) = filter.to_sql(0);
1952 assert!(sql.contains("OR"));
1953 }
1954
1955 #[test]
1956 fn test_fluent_builder_with_capacity() {
1957 let filter = Filter::builder()
1958 .with_capacity(5)
1959 .eq("a", 1)
1960 .ne("b", 2)
1961 .lt("c", 3)
1962 .lte("d", 4)
1963 .gte("e", 5)
1964 .build_and();
1965
1966 let (sql, params) = filter.to_sql(0);
1967 assert!(sql.contains("AND"));
1968 assert_eq!(params.len(), 5);
1969 }
1970
1971 #[test]
1972 fn test_fluent_builder_string_operations() {
1973 let filter = Filter::builder()
1974 .contains("name", "john")
1975 .starts_with("email", "admin")
1976 .ends_with("domain", ".com")
1977 .build_and();
1978
1979 let (sql, _) = filter.to_sql(0);
1980 assert!(sql.contains("LIKE"));
1981 }
1982
1983 #[test]
1984 fn test_fluent_builder_null_operations() {
1985 let filter = Filter::builder()
1986 .is_null("deleted_at")
1987 .is_not_null("created_at")
1988 .build_and();
1989
1990 let (sql, _) = filter.to_sql(0);
1991 assert!(sql.contains("IS NULL"));
1992 assert!(sql.contains("IS NOT NULL"));
1993 }
1994
1995 #[test]
1996 fn test_fluent_builder_in_operations() {
1997 let filter = Filter::builder()
1998 .is_in("status", vec!["pending", "processing"])
1999 .not_in("role", vec!["banned", "suspended"])
2000 .build_and();
2001
2002 let (sql, _) = filter.to_sql(0);
2003 assert!(sql.contains("IN"));
2004 assert!(sql.contains("NOT IN"));
2005 }
2006
2007 #[test]
2008 fn test_fluent_builder_filter_if() {
2009 let include_archived = false;
2010 let filter = Filter::builder()
2011 .eq("active", true)
2012 .filter_if(
2013 include_archived,
2014 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2015 )
2016 .build_and();
2017
2018 assert!(matches!(filter, Filter::Equals(_, _)));
2020 }
2021
2022 #[test]
2023 fn test_fluent_builder_filter_if_some() {
2024 let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2025 let filter = Filter::builder()
2026 .eq("id", 1)
2027 .filter_if_some(maybe_status)
2028 .build_and();
2029
2030 assert!(matches!(filter, Filter::And(_)));
2031 }
2032
2033 #[test]
2034 fn test_and_builder_extend() {
2035 let extra_filters = vec![
2036 Filter::Gt("score".into(), FilterValue::Int(100)),
2037 Filter::Lt("score".into(), FilterValue::Int(1000)),
2038 ];
2039
2040 let filter = Filter::and_builder(3)
2041 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2042 .extend(extra_filters)
2043 .build();
2044
2045 let (sql, params) = filter.to_sql(0);
2046 assert!(sql.contains("AND"));
2047 assert_eq!(params.len(), 3);
2048 }
2049
2050 #[test]
2051 fn test_builder_len_and_is_empty() {
2052 let mut builder = AndFilterBuilder::new();
2053 assert!(builder.is_empty());
2054 assert_eq!(builder.len(), 0);
2055
2056 builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2057 assert!(!builder.is_empty());
2058 assert_eq!(builder.len(), 1);
2059 }
2060
2061 #[test]
2064 fn test_and2_both_valid() {
2065 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2066 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2067 let filter = Filter::and2(a, b);
2068
2069 assert!(matches!(filter, Filter::And(_)));
2070 let (sql, params) = filter.to_sql(0);
2071 assert!(sql.contains("AND"));
2072 assert_eq!(params.len(), 2);
2073 }
2074
2075 #[test]
2076 fn test_and2_first_none() {
2077 let a = Filter::None;
2078 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2079 let filter = Filter::and2(a, b.clone());
2080
2081 assert_eq!(filter, b);
2082 }
2083
2084 #[test]
2085 fn test_and2_second_none() {
2086 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2087 let b = Filter::None;
2088 let filter = Filter::and2(a.clone(), b);
2089
2090 assert_eq!(filter, a);
2091 }
2092
2093 #[test]
2094 fn test_and2_both_none() {
2095 let filter = Filter::and2(Filter::None, Filter::None);
2096 assert!(filter.is_none());
2097 }
2098
2099 #[test]
2100 fn test_or2_both_valid() {
2101 let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2102 let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2103 let filter = Filter::or2(a, b);
2104
2105 assert!(matches!(filter, Filter::Or(_)));
2106 let (sql, _) = filter.to_sql(0);
2107 assert!(sql.contains("OR"));
2108 }
2109
2110 #[test]
2111 fn test_or2_first_none() {
2112 let a = Filter::None;
2113 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2114 let filter = Filter::or2(a, b.clone());
2115
2116 assert_eq!(filter, b);
2117 }
2118
2119 #[test]
2120 fn test_or2_second_none() {
2121 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2122 let b = Filter::None;
2123 let filter = Filter::or2(a.clone(), b);
2124
2125 assert_eq!(filter, a);
2126 }
2127
2128 #[test]
2129 fn test_or2_both_none() {
2130 let filter = Filter::or2(Filter::None, Filter::None);
2131 assert!(filter.is_none());
2132 }
2133}