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
173impl From<bool> for FilterValue {
174 fn from(v: bool) -> Self {
175 Self::Bool(v)
176 }
177}
178
179impl From<i32> for FilterValue {
180 fn from(v: i32) -> Self {
181 Self::Int(v as i64)
182 }
183}
184
185impl From<i64> for FilterValue {
186 fn from(v: i64) -> Self {
187 Self::Int(v)
188 }
189}
190
191impl From<f64> for FilterValue {
192 fn from(v: f64) -> Self {
193 Self::Float(v)
194 }
195}
196
197impl From<String> for FilterValue {
198 fn from(v: String) -> Self {
199 Self::String(v)
200 }
201}
202
203impl From<&str> for FilterValue {
204 fn from(v: &str) -> Self {
205 Self::String(v.to_string())
206 }
207}
208
209impl<T: Into<FilterValue>> From<Vec<T>> for FilterValue {
210 fn from(v: Vec<T>) -> Self {
211 Self::List(v.into_iter().map(Into::into).collect())
212 }
213}
214
215impl<T: Into<FilterValue>> From<Option<T>> for FilterValue {
216 fn from(v: Option<T>) -> Self {
217 match v {
218 Some(v) => v.into(),
219 None => Self::Null,
220 }
221 }
222}
223
224impl From<i8> for FilterValue {
230 fn from(v: i8) -> Self {
231 Self::Int(v as i64)
232 }
233}
234impl From<i16> for FilterValue {
235 fn from(v: i16) -> Self {
236 Self::Int(v as i64)
237 }
238}
239impl From<u8> for FilterValue {
240 fn from(v: u8) -> Self {
241 Self::Int(v as i64)
242 }
243}
244impl From<u16> for FilterValue {
245 fn from(v: u16) -> Self {
246 Self::Int(v as i64)
247 }
248}
249impl From<u32> for FilterValue {
250 fn from(v: u32) -> Self {
251 Self::Int(v as i64)
252 }
253}
254impl From<u64> for FilterValue {
260 fn from(v: u64) -> Self {
261 let v = i64::try_from(v).expect(
262 "u64 value exceeds i64::MAX; cast explicitly to i64 or use FilterValue::String",
263 );
264 Self::Int(v)
265 }
266}
267
268impl From<f32> for FilterValue {
269 fn from(v: f32) -> Self {
270 Self::Float(f64::from(v))
271 }
272}
273
274impl From<chrono::DateTime<chrono::Utc>> for FilterValue {
286 fn from(v: chrono::DateTime<chrono::Utc>) -> Self {
287 Self::String(v.to_rfc3339_opts(chrono::SecondsFormat::Micros, true))
289 }
290}
291impl From<chrono::NaiveDateTime> for FilterValue {
292 fn from(v: chrono::NaiveDateTime) -> Self {
293 Self::String(v.format("%Y-%m-%dT%H:%M:%S%.6f").to_string())
296 }
297}
298impl From<chrono::NaiveDate> for FilterValue {
299 fn from(v: chrono::NaiveDate) -> Self {
300 Self::String(v.format("%Y-%m-%d").to_string())
301 }
302}
303impl From<chrono::NaiveTime> for FilterValue {
304 fn from(v: chrono::NaiveTime) -> Self {
305 Self::String(v.format("%H:%M:%S%.6f").to_string())
306 }
307}
308
309impl From<uuid::Uuid> for FilterValue {
310 fn from(v: uuid::Uuid) -> Self {
311 Self::String(v.to_string())
312 }
313}
314
315impl From<rust_decimal::Decimal> for FilterValue {
316 fn from(v: rust_decimal::Decimal) -> Self {
317 Self::String(v.to_string())
318 }
319}
320
321impl From<serde_json::Value> for FilterValue {
322 fn from(v: serde_json::Value) -> Self {
323 Self::Json(v)
324 }
325}
326
327pub trait ToFilterValue {
349 fn to_filter_value(&self) -> FilterValue;
351}
352
353impl ToFilterValue for i8 {
354 fn to_filter_value(&self) -> FilterValue {
355 FilterValue::Int(*self as i64)
356 }
357}
358impl ToFilterValue for i16 {
359 fn to_filter_value(&self) -> FilterValue {
360 FilterValue::Int(*self as i64)
361 }
362}
363impl ToFilterValue for i32 {
364 fn to_filter_value(&self) -> FilterValue {
365 FilterValue::Int(*self as i64)
366 }
367}
368impl ToFilterValue for i64 {
369 fn to_filter_value(&self) -> FilterValue {
370 FilterValue::Int(*self)
371 }
372}
373impl ToFilterValue for u8 {
374 fn to_filter_value(&self) -> FilterValue {
375 FilterValue::Int(*self as i64)
376 }
377}
378impl ToFilterValue for u16 {
379 fn to_filter_value(&self) -> FilterValue {
380 FilterValue::Int(*self as i64)
381 }
382}
383impl ToFilterValue for u32 {
384 fn to_filter_value(&self) -> FilterValue {
385 FilterValue::Int(*self as i64)
386 }
387}
388impl ToFilterValue for f32 {
389 fn to_filter_value(&self) -> FilterValue {
390 FilterValue::Float(f64::from(*self))
391 }
392}
393impl ToFilterValue for f64 {
394 fn to_filter_value(&self) -> FilterValue {
395 FilterValue::Float(*self)
396 }
397}
398impl ToFilterValue for bool {
399 fn to_filter_value(&self) -> FilterValue {
400 FilterValue::Bool(*self)
401 }
402}
403impl ToFilterValue for String {
404 fn to_filter_value(&self) -> FilterValue {
405 FilterValue::String(self.clone())
406 }
407}
408impl ToFilterValue for str {
409 fn to_filter_value(&self) -> FilterValue {
410 FilterValue::String(self.to_string())
411 }
412}
413impl ToFilterValue for uuid::Uuid {
414 fn to_filter_value(&self) -> FilterValue {
415 FilterValue::String(self.to_string())
416 }
417}
418impl ToFilterValue for rust_decimal::Decimal {
419 fn to_filter_value(&self) -> FilterValue {
420 FilterValue::String(self.to_string())
421 }
422}
423impl ToFilterValue for chrono::DateTime<chrono::Utc> {
424 fn to_filter_value(&self) -> FilterValue {
425 FilterValue::String(self.to_rfc3339_opts(chrono::SecondsFormat::Micros, true))
427 }
428}
429impl ToFilterValue for chrono::NaiveDateTime {
430 fn to_filter_value(&self) -> FilterValue {
431 FilterValue::String(self.format("%Y-%m-%dT%H:%M:%S%.6f").to_string())
432 }
433}
434impl ToFilterValue for chrono::NaiveDate {
435 fn to_filter_value(&self) -> FilterValue {
436 FilterValue::String(self.format("%Y-%m-%d").to_string())
437 }
438}
439impl ToFilterValue for chrono::NaiveTime {
440 fn to_filter_value(&self) -> FilterValue {
441 FilterValue::String(self.format("%H:%M:%S%.6f").to_string())
442 }
443}
444impl ToFilterValue for serde_json::Value {
445 fn to_filter_value(&self) -> FilterValue {
446 FilterValue::Json(self.clone())
447 }
448}
449impl ToFilterValue for Vec<u8> {
450 fn to_filter_value(&self) -> FilterValue {
451 FilterValue::List(self.iter().map(|b| FilterValue::Int(*b as i64)).collect())
455 }
456}
457impl<T: ToFilterValue> ToFilterValue for Option<T> {
458 fn to_filter_value(&self) -> FilterValue {
459 self.as_ref()
460 .map(T::to_filter_value)
461 .unwrap_or(FilterValue::Null)
462 }
463}
464
465#[derive(Debug, Clone, PartialEq)]
467pub enum ScalarFilter<T> {
468 Equals(T),
470 Not(Box<T>),
472 In(Vec<T>),
474 NotIn(Vec<T>),
476 Lt(T),
478 Lte(T),
480 Gt(T),
482 Gte(T),
484 Contains(T),
486 StartsWith(T),
488 EndsWith(T),
490 IsNull,
492 IsNotNull,
494}
495
496impl<T: Into<FilterValue>> ScalarFilter<T> {
497 pub fn into_filter(self, column: impl Into<FieldName>) -> Filter {
502 let column = column.into();
503 match self {
504 Self::Equals(v) => Filter::Equals(column, v.into()),
505 Self::Not(v) => Filter::NotEquals(column, (*v).into()),
506 Self::In(values) => Filter::In(column, values.into_iter().map(Into::into).collect()),
507 Self::NotIn(values) => {
508 Filter::NotIn(column, values.into_iter().map(Into::into).collect())
509 }
510 Self::Lt(v) => Filter::Lt(column, v.into()),
511 Self::Lte(v) => Filter::Lte(column, v.into()),
512 Self::Gt(v) => Filter::Gt(column, v.into()),
513 Self::Gte(v) => Filter::Gte(column, v.into()),
514 Self::Contains(v) => Filter::Contains(column, v.into()),
515 Self::StartsWith(v) => Filter::StartsWith(column, v.into()),
516 Self::EndsWith(v) => Filter::EndsWith(column, v.into()),
517 Self::IsNull => Filter::IsNull(column),
518 Self::IsNotNull => Filter::IsNotNull(column),
519 }
520 }
521}
522
523#[derive(Debug, Clone, PartialEq)]
544#[repr(C)] #[derive(Default)]
546pub enum Filter {
547 #[default]
549 None,
550
551 Equals(FieldName, FilterValue),
553 NotEquals(FieldName, FilterValue),
555
556 Lt(FieldName, FilterValue),
558 Lte(FieldName, FilterValue),
560 Gt(FieldName, FilterValue),
562 Gte(FieldName, FilterValue),
564
565 In(FieldName, ValueList),
567 NotIn(FieldName, ValueList),
569
570 Contains(FieldName, FilterValue),
572 StartsWith(FieldName, FilterValue),
574 EndsWith(FieldName, FilterValue),
576
577 IsNull(FieldName),
579 IsNotNull(FieldName),
581
582 And(Box<[Filter]>),
587 Or(Box<[Filter]>),
592 Not(Box<Filter>),
594}
595
596impl Filter {
597 #[inline(always)]
599 pub fn none() -> Self {
600 Self::None
601 }
602
603 #[inline(always)]
605 pub fn is_none(&self) -> bool {
606 matches!(self, Self::None)
607 }
608
609 #[inline]
615 pub fn and(filters: impl IntoIterator<Item = Filter>) -> Self {
616 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
617 let count = filters.len();
618 let result = match count {
619 0 => Self::None,
620 1 => filters.into_iter().next().unwrap(),
621 _ => Self::And(filters.into_boxed_slice()),
622 };
623 debug!(count, "Filter::and() created");
624 result
625 }
626
627 #[inline(always)]
631 pub fn and2(a: Filter, b: Filter) -> Self {
632 match (a.is_none(), b.is_none()) {
633 (true, true) => Self::None,
634 (true, false) => b,
635 (false, true) => a,
636 (false, false) => Self::And(Box::new([a, b])),
637 }
638 }
639
640 #[inline]
646 pub fn or(filters: impl IntoIterator<Item = Filter>) -> Self {
647 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
648 let count = filters.len();
649 let result = match count {
650 0 => Self::None,
651 1 => filters.into_iter().next().unwrap(),
652 _ => Self::Or(filters.into_boxed_slice()),
653 };
654 debug!(count, "Filter::or() created");
655 result
656 }
657
658 #[inline(always)]
662 pub fn or2(a: Filter, b: Filter) -> Self {
663 match (a.is_none(), b.is_none()) {
664 (true, true) => Self::None,
665 (true, false) => b,
666 (false, true) => a,
667 (false, false) => Self::Or(Box::new([a, b])),
668 }
669 }
670
671 #[inline(always)]
692 pub fn and_n<const N: usize>(filters: [Filter; N]) -> Self {
693 Self::And(Box::new(filters))
695 }
696
697 #[inline(always)]
702 pub fn or_n<const N: usize>(filters: [Filter; N]) -> Self {
703 Self::Or(Box::new(filters))
704 }
705
706 #[inline(always)]
708 pub fn and3(a: Filter, b: Filter, c: Filter) -> Self {
709 Self::And(Box::new([a, b, c]))
710 }
711
712 #[inline(always)]
714 pub fn and4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
715 Self::And(Box::new([a, b, c, d]))
716 }
717
718 #[inline(always)]
720 pub fn and5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
721 Self::And(Box::new([a, b, c, d, e]))
722 }
723
724 #[inline(always)]
726 pub fn or3(a: Filter, b: Filter, c: Filter) -> Self {
727 Self::Or(Box::new([a, b, c]))
728 }
729
730 #[inline(always)]
732 pub fn or4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
733 Self::Or(Box::new([a, b, c, d]))
734 }
735
736 #[inline(always)]
738 pub fn or5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
739 Self::Or(Box::new([a, b, c, d, e]))
740 }
741
742 #[inline]
751 pub fn in_i64(field: impl Into<FieldName>, values: impl IntoIterator<Item = i64>) -> Self {
752 let list: ValueList = values.into_iter().map(FilterValue::Int).collect();
753 Self::In(field.into(), list)
754 }
755
756 #[inline]
758 pub fn in_i32(field: impl Into<FieldName>, values: impl IntoIterator<Item = i32>) -> Self {
759 let list: ValueList = values
760 .into_iter()
761 .map(|v| FilterValue::Int(v as i64))
762 .collect();
763 Self::In(field.into(), list)
764 }
765
766 #[inline]
768 pub fn in_strings(
769 field: impl Into<FieldName>,
770 values: impl IntoIterator<Item = String>,
771 ) -> Self {
772 let list: ValueList = values.into_iter().map(FilterValue::String).collect();
773 Self::In(field.into(), list)
774 }
775
776 #[inline]
780 pub fn in_values(field: impl Into<FieldName>, values: ValueList) -> Self {
781 Self::In(field.into(), values)
782 }
783
784 #[inline]
788 pub fn in_range(field: impl Into<FieldName>, range: std::ops::Range<i64>) -> Self {
789 let list: ValueList = range.map(FilterValue::Int).collect();
790 Self::In(field.into(), list)
791 }
792
793 #[inline(always)]
798 pub fn in_i64_slice(field: impl Into<FieldName>, values: &[i64]) -> Self {
799 let mut list = Vec::with_capacity(values.len());
800 for &v in values {
801 list.push(FilterValue::Int(v));
802 }
803 Self::In(field.into(), list)
804 }
805
806 #[inline(always)]
808 pub fn in_i32_slice(field: impl Into<FieldName>, values: &[i32]) -> Self {
809 let mut list = Vec::with_capacity(values.len());
810 for &v in values {
811 list.push(FilterValue::Int(v as i64));
812 }
813 Self::In(field.into(), list)
814 }
815
816 #[inline(always)]
818 pub fn in_str_slice(field: impl Into<FieldName>, values: &[&str]) -> Self {
819 let mut list = Vec::with_capacity(values.len());
820 for &v in values {
821 list.push(FilterValue::String(v.to_string()));
822 }
823 Self::In(field.into(), list)
824 }
825
826 #[inline]
828 #[allow(clippy::should_implement_trait)]
829 pub fn not(filter: Filter) -> Self {
830 if filter.is_none() {
831 return Self::None;
832 }
833 Self::Not(Box::new(filter))
834 }
835
836 #[inline]
850 pub fn in_slice<T: Into<FilterValue> + Clone>(
851 field: impl Into<FieldName>,
852 values: &[T],
853 ) -> Self {
854 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
855 Self::In(field.into(), list)
856 }
857
858 #[inline]
869 pub fn not_in_slice<T: Into<FilterValue> + Clone>(
870 field: impl Into<FieldName>,
871 values: &[T],
872 ) -> Self {
873 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
874 Self::NotIn(field.into(), list)
875 }
876
877 #[inline]
889 pub fn in_array<T: Into<FilterValue>, const N: usize>(
890 field: impl Into<FieldName>,
891 values: [T; N],
892 ) -> Self {
893 let list: ValueList = values.into_iter().map(Into::into).collect();
894 Self::In(field.into(), list)
895 }
896
897 #[inline]
899 pub fn not_in_array<T: Into<FilterValue>, const N: usize>(
900 field: impl Into<FieldName>,
901 values: [T; N],
902 ) -> Self {
903 let list: ValueList = values.into_iter().map(Into::into).collect();
904 Self::NotIn(field.into(), list)
905 }
906
907 pub fn and_then(self, other: Filter) -> Self {
909 if self.is_none() {
910 return other;
911 }
912 if other.is_none() {
913 return self;
914 }
915 match self {
916 Self::And(filters) => {
917 let mut vec: Vec<_> = filters.into_vec();
919 vec.push(other);
920 Self::And(vec.into_boxed_slice())
921 }
922 _ => Self::And(Box::new([self, other])),
923 }
924 }
925
926 pub fn or_else(self, other: Filter) -> Self {
928 if self.is_none() {
929 return other;
930 }
931 if other.is_none() {
932 return self;
933 }
934 match self {
935 Self::Or(filters) => {
936 let mut vec: Vec<_> = filters.into_vec();
938 vec.push(other);
939 Self::Or(vec.into_boxed_slice())
940 }
941 _ => Self::Or(Box::new([self, other])),
942 }
943 }
944
945 pub fn to_sql(
948 &self,
949 param_offset: usize,
950 dialect: &dyn crate::dialect::SqlDialect,
951 ) -> (String, Vec<FilterValue>) {
952 let mut params = Vec::new();
953 let sql = self.to_sql_with_params(param_offset, &mut params, dialect);
954 (sql, params)
955 }
956
957 fn to_sql_with_params(
958 &self,
959 mut param_idx: usize,
960 params: &mut Vec<FilterValue>,
961 dialect: &dyn crate::dialect::SqlDialect,
962 ) -> String {
963 match self {
964 Self::None => "TRUE".to_string(),
965
966 Self::Equals(col, val) => {
967 let c = dialect.quote_ident(col);
968 if val.is_null() {
969 format!("{} IS NULL", c)
970 } else {
971 params.push(val.clone());
972 param_idx += params.len();
973 format!("{} = {}", c, dialect.placeholder(param_idx))
974 }
975 }
976 Self::NotEquals(col, val) => {
977 let c = dialect.quote_ident(col);
978 if val.is_null() {
979 format!("{} IS NOT NULL", c)
980 } else {
981 params.push(val.clone());
982 param_idx += params.len();
983 format!("{} != {}", c, dialect.placeholder(param_idx))
984 }
985 }
986
987 Self::Lt(col, val) => {
988 let c = dialect.quote_ident(col);
989 params.push(val.clone());
990 param_idx += params.len();
991 format!("{} < {}", c, dialect.placeholder(param_idx))
992 }
993 Self::Lte(col, val) => {
994 let c = dialect.quote_ident(col);
995 params.push(val.clone());
996 param_idx += params.len();
997 format!("{} <= {}", c, dialect.placeholder(param_idx))
998 }
999 Self::Gt(col, val) => {
1000 let c = dialect.quote_ident(col);
1001 params.push(val.clone());
1002 param_idx += params.len();
1003 format!("{} > {}", c, dialect.placeholder(param_idx))
1004 }
1005 Self::Gte(col, val) => {
1006 let c = dialect.quote_ident(col);
1007 params.push(val.clone());
1008 param_idx += params.len();
1009 format!("{} >= {}", c, dialect.placeholder(param_idx))
1010 }
1011
1012 Self::In(col, values) => {
1013 if values.is_empty() {
1014 return "FALSE".to_string();
1015 }
1016 let c = dialect.quote_ident(col);
1017 let placeholders: Vec<_> = values
1018 .iter()
1019 .map(|v| {
1020 params.push(v.clone());
1021 param_idx += params.len();
1022 dialect.placeholder(param_idx)
1023 })
1024 .collect();
1025 format!("{} IN ({})", c, placeholders.join(", "))
1026 }
1027 Self::NotIn(col, values) => {
1028 if values.is_empty() {
1029 return "TRUE".to_string();
1030 }
1031 let c = dialect.quote_ident(col);
1032 let placeholders: Vec<_> = values
1033 .iter()
1034 .map(|v| {
1035 params.push(v.clone());
1036 param_idx += params.len();
1037 dialect.placeholder(param_idx)
1038 })
1039 .collect();
1040 format!("{} NOT IN ({})", c, placeholders.join(", "))
1041 }
1042
1043 Self::Contains(col, val) => {
1044 let c = dialect.quote_ident(col);
1045 if let FilterValue::String(s) = val {
1046 params.push(FilterValue::String(format!("%{}%", s)));
1047 } else {
1048 params.push(val.clone());
1049 }
1050 param_idx += params.len();
1051 format!("{} LIKE {}", c, dialect.placeholder(param_idx))
1052 }
1053 Self::StartsWith(col, val) => {
1054 let c = dialect.quote_ident(col);
1055 if let FilterValue::String(s) = val {
1056 params.push(FilterValue::String(format!("{}%", s)));
1057 } else {
1058 params.push(val.clone());
1059 }
1060 param_idx += params.len();
1061 format!("{} LIKE {}", c, dialect.placeholder(param_idx))
1062 }
1063 Self::EndsWith(col, val) => {
1064 let c = dialect.quote_ident(col);
1065 if let FilterValue::String(s) = val {
1066 params.push(FilterValue::String(format!("%{}", s)));
1067 } else {
1068 params.push(val.clone());
1069 }
1070 param_idx += params.len();
1071 format!("{} LIKE {}", c, dialect.placeholder(param_idx))
1072 }
1073
1074 Self::IsNull(col) => {
1075 let c = dialect.quote_ident(col);
1076 format!("{} IS NULL", c)
1077 }
1078 Self::IsNotNull(col) => {
1079 let c = dialect.quote_ident(col);
1080 format!("{} IS NOT NULL", c)
1081 }
1082
1083 Self::And(filters) => {
1084 if filters.is_empty() {
1085 return "TRUE".to_string();
1086 }
1087 let parts: Vec<_> = filters
1088 .iter()
1089 .map(|f| f.to_sql_with_params(param_idx + params.len(), params, dialect))
1090 .collect();
1091 format!("({})", parts.join(" AND "))
1092 }
1093 Self::Or(filters) => {
1094 if filters.is_empty() {
1095 return "FALSE".to_string();
1096 }
1097 let parts: Vec<_> = filters
1098 .iter()
1099 .map(|f| f.to_sql_with_params(param_idx + params.len(), params, dialect))
1100 .collect();
1101 format!("({})", parts.join(" OR "))
1102 }
1103 Self::Not(filter) => {
1104 let inner = filter.to_sql_with_params(param_idx, params, dialect);
1105 format!("NOT ({})", inner)
1106 }
1107 }
1108 }
1109
1110 #[inline]
1128 pub fn and_builder(capacity: usize) -> AndFilterBuilder {
1129 AndFilterBuilder::with_capacity(capacity)
1130 }
1131
1132 #[inline]
1149 pub fn or_builder(capacity: usize) -> OrFilterBuilder {
1150 OrFilterBuilder::with_capacity(capacity)
1151 }
1152
1153 #[inline]
1169 pub fn builder() -> FluentFilterBuilder {
1170 FluentFilterBuilder::new()
1171 }
1172}
1173
1174#[derive(Debug, Clone)]
1178pub struct AndFilterBuilder {
1179 filters: Vec<Filter>,
1180}
1181
1182impl AndFilterBuilder {
1183 #[inline]
1185 pub fn new() -> Self {
1186 Self {
1187 filters: Vec::new(),
1188 }
1189 }
1190
1191 #[inline]
1193 pub fn with_capacity(capacity: usize) -> Self {
1194 Self {
1195 filters: Vec::with_capacity(capacity),
1196 }
1197 }
1198
1199 #[inline]
1201 pub fn push(mut self, filter: Filter) -> Self {
1202 if !filter.is_none() {
1203 self.filters.push(filter);
1204 }
1205 self
1206 }
1207
1208 #[inline]
1210 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1211 self.filters
1212 .extend(filters.into_iter().filter(|f| !f.is_none()));
1213 self
1214 }
1215
1216 #[inline]
1218 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1219 if condition { self.push(filter) } else { self }
1220 }
1221
1222 #[inline]
1224 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1225 where
1226 F: Into<Filter>,
1227 {
1228 match opt {
1229 Some(f) => self.push(f.into()),
1230 None => self,
1231 }
1232 }
1233
1234 #[inline]
1236 pub fn build(self) -> Filter {
1237 match self.filters.len() {
1238 0 => Filter::None,
1239 1 => self.filters.into_iter().next().unwrap(),
1240 _ => Filter::And(self.filters.into_boxed_slice()),
1241 }
1242 }
1243
1244 #[inline]
1246 pub fn len(&self) -> usize {
1247 self.filters.len()
1248 }
1249
1250 #[inline]
1252 pub fn is_empty(&self) -> bool {
1253 self.filters.is_empty()
1254 }
1255}
1256
1257impl Default for AndFilterBuilder {
1258 fn default() -> Self {
1259 Self::new()
1260 }
1261}
1262
1263#[derive(Debug, Clone)]
1267pub struct OrFilterBuilder {
1268 filters: Vec<Filter>,
1269}
1270
1271impl OrFilterBuilder {
1272 #[inline]
1274 pub fn new() -> Self {
1275 Self {
1276 filters: Vec::new(),
1277 }
1278 }
1279
1280 #[inline]
1282 pub fn with_capacity(capacity: usize) -> Self {
1283 Self {
1284 filters: Vec::with_capacity(capacity),
1285 }
1286 }
1287
1288 #[inline]
1290 pub fn push(mut self, filter: Filter) -> Self {
1291 if !filter.is_none() {
1292 self.filters.push(filter);
1293 }
1294 self
1295 }
1296
1297 #[inline]
1299 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1300 self.filters
1301 .extend(filters.into_iter().filter(|f| !f.is_none()));
1302 self
1303 }
1304
1305 #[inline]
1307 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1308 if condition { self.push(filter) } else { self }
1309 }
1310
1311 #[inline]
1313 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1314 where
1315 F: Into<Filter>,
1316 {
1317 match opt {
1318 Some(f) => self.push(f.into()),
1319 None => self,
1320 }
1321 }
1322
1323 #[inline]
1325 pub fn build(self) -> Filter {
1326 match self.filters.len() {
1327 0 => Filter::None,
1328 1 => self.filters.into_iter().next().unwrap(),
1329 _ => Filter::Or(self.filters.into_boxed_slice()),
1330 }
1331 }
1332
1333 #[inline]
1335 pub fn len(&self) -> usize {
1336 self.filters.len()
1337 }
1338
1339 #[inline]
1341 pub fn is_empty(&self) -> bool {
1342 self.filters.is_empty()
1343 }
1344}
1345
1346impl Default for OrFilterBuilder {
1347 fn default() -> Self {
1348 Self::new()
1349 }
1350}
1351
1352#[derive(Debug, Clone)]
1377pub struct FluentFilterBuilder {
1378 filters: Vec<Filter>,
1379}
1380
1381impl FluentFilterBuilder {
1382 #[inline]
1384 pub fn new() -> Self {
1385 Self {
1386 filters: Vec::new(),
1387 }
1388 }
1389
1390 #[inline]
1392 pub fn with_capacity(mut self, capacity: usize) -> Self {
1393 self.filters.reserve(capacity);
1394 self
1395 }
1396
1397 #[inline]
1399 pub fn eq<F, V>(mut self, field: F, value: V) -> Self
1400 where
1401 F: Into<FieldName>,
1402 V: Into<FilterValue>,
1403 {
1404 self.filters
1405 .push(Filter::Equals(field.into(), value.into()));
1406 self
1407 }
1408
1409 #[inline]
1411 pub fn ne<F, V>(mut self, field: F, value: V) -> Self
1412 where
1413 F: Into<FieldName>,
1414 V: Into<FilterValue>,
1415 {
1416 self.filters
1417 .push(Filter::NotEquals(field.into(), value.into()));
1418 self
1419 }
1420
1421 #[inline]
1423 pub fn lt<F, V>(mut self, field: F, value: V) -> Self
1424 where
1425 F: Into<FieldName>,
1426 V: Into<FilterValue>,
1427 {
1428 self.filters.push(Filter::Lt(field.into(), value.into()));
1429 self
1430 }
1431
1432 #[inline]
1434 pub fn lte<F, V>(mut self, field: F, value: V) -> Self
1435 where
1436 F: Into<FieldName>,
1437 V: Into<FilterValue>,
1438 {
1439 self.filters.push(Filter::Lte(field.into(), value.into()));
1440 self
1441 }
1442
1443 #[inline]
1445 pub fn gt<F, V>(mut self, field: F, value: V) -> Self
1446 where
1447 F: Into<FieldName>,
1448 V: Into<FilterValue>,
1449 {
1450 self.filters.push(Filter::Gt(field.into(), value.into()));
1451 self
1452 }
1453
1454 #[inline]
1456 pub fn gte<F, V>(mut self, field: F, value: V) -> Self
1457 where
1458 F: Into<FieldName>,
1459 V: Into<FilterValue>,
1460 {
1461 self.filters.push(Filter::Gte(field.into(), value.into()));
1462 self
1463 }
1464
1465 #[inline]
1467 pub fn is_in<F, I, V>(mut self, field: F, values: I) -> Self
1468 where
1469 F: Into<FieldName>,
1470 I: IntoIterator<Item = V>,
1471 V: Into<FilterValue>,
1472 {
1473 self.filters.push(Filter::In(
1474 field.into(),
1475 values.into_iter().map(Into::into).collect(),
1476 ));
1477 self
1478 }
1479
1480 #[inline]
1482 pub fn not_in<F, I, V>(mut self, field: F, values: I) -> Self
1483 where
1484 F: Into<FieldName>,
1485 I: IntoIterator<Item = V>,
1486 V: Into<FilterValue>,
1487 {
1488 self.filters.push(Filter::NotIn(
1489 field.into(),
1490 values.into_iter().map(Into::into).collect(),
1491 ));
1492 self
1493 }
1494
1495 #[inline]
1497 pub fn contains<F, V>(mut self, field: F, value: V) -> Self
1498 where
1499 F: Into<FieldName>,
1500 V: Into<FilterValue>,
1501 {
1502 self.filters
1503 .push(Filter::Contains(field.into(), value.into()));
1504 self
1505 }
1506
1507 #[inline]
1509 pub fn starts_with<F, V>(mut self, field: F, value: V) -> Self
1510 where
1511 F: Into<FieldName>,
1512 V: Into<FilterValue>,
1513 {
1514 self.filters
1515 .push(Filter::StartsWith(field.into(), value.into()));
1516 self
1517 }
1518
1519 #[inline]
1521 pub fn ends_with<F, V>(mut self, field: F, value: V) -> Self
1522 where
1523 F: Into<FieldName>,
1524 V: Into<FilterValue>,
1525 {
1526 self.filters
1527 .push(Filter::EndsWith(field.into(), value.into()));
1528 self
1529 }
1530
1531 #[inline]
1533 pub fn is_null<F>(mut self, field: F) -> Self
1534 where
1535 F: Into<FieldName>,
1536 {
1537 self.filters.push(Filter::IsNull(field.into()));
1538 self
1539 }
1540
1541 #[inline]
1543 pub fn is_not_null<F>(mut self, field: F) -> Self
1544 where
1545 F: Into<FieldName>,
1546 {
1547 self.filters.push(Filter::IsNotNull(field.into()));
1548 self
1549 }
1550
1551 #[inline]
1553 pub fn filter(mut self, filter: Filter) -> Self {
1554 if !filter.is_none() {
1555 self.filters.push(filter);
1556 }
1557 self
1558 }
1559
1560 #[inline]
1562 pub fn filter_if(self, condition: bool, filter: Filter) -> Self {
1563 if condition { self.filter(filter) } else { self }
1564 }
1565
1566 #[inline]
1568 pub fn filter_if_some<F>(self, opt: Option<F>) -> Self
1569 where
1570 F: Into<Filter>,
1571 {
1572 match opt {
1573 Some(f) => self.filter(f.into()),
1574 None => self,
1575 }
1576 }
1577
1578 #[inline]
1580 pub fn build_and(self) -> Filter {
1581 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1582 match filters.len() {
1583 0 => Filter::None,
1584 1 => filters.into_iter().next().unwrap(),
1585 _ => Filter::And(filters.into_boxed_slice()),
1586 }
1587 }
1588
1589 #[inline]
1591 pub fn build_or(self) -> Filter {
1592 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1593 match filters.len() {
1594 0 => Filter::None,
1595 1 => filters.into_iter().next().unwrap(),
1596 _ => Filter::Or(filters.into_boxed_slice()),
1597 }
1598 }
1599
1600 #[inline]
1602 pub fn len(&self) -> usize {
1603 self.filters.len()
1604 }
1605
1606 #[inline]
1608 pub fn is_empty(&self) -> bool {
1609 self.filters.is_empty()
1610 }
1611}
1612
1613impl Default for FluentFilterBuilder {
1614 fn default() -> Self {
1615 Self::new()
1616 }
1617}
1618
1619#[cfg(test)]
1620mod tests {
1621 use super::*;
1622
1623 #[test]
1624 fn test_filter_value_from() {
1625 assert_eq!(FilterValue::from(42i32), FilterValue::Int(42));
1626 assert_eq!(
1627 FilterValue::from("hello"),
1628 FilterValue::String("hello".to_string())
1629 );
1630 assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
1631 }
1632
1633 #[test]
1634 fn test_scalar_filter_equals() {
1635 let filter = ScalarFilter::Equals("test@example.com".to_string()).into_filter("email");
1636
1637 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1638 assert_eq!(sql, r#""email" = $1"#);
1639 assert_eq!(params.len(), 1);
1640 }
1641
1642 #[test]
1643 fn test_filter_and() {
1644 let f1 = Filter::Equals("name".into(), "Alice".into());
1645 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1646 let combined = Filter::and([f1, f2]);
1647
1648 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
1649 assert!(sql.contains("AND"));
1650 assert_eq!(params.len(), 2);
1651 }
1652
1653 #[test]
1654 fn test_filter_or() {
1655 let f1 = Filter::Equals("status".into(), "active".into());
1656 let f2 = Filter::Equals("status".into(), "pending".into());
1657 let combined = Filter::or([f1, f2]);
1658
1659 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
1660 assert!(sql.contains("OR"));
1661 }
1662
1663 #[test]
1664 fn test_filter_not() {
1665 let filter = Filter::not(Filter::Equals("deleted".into(), FilterValue::Bool(true)));
1666
1667 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
1668 assert!(sql.contains("NOT"));
1669 }
1670
1671 #[test]
1672 fn test_filter_is_null() {
1673 let filter = Filter::IsNull("deleted_at".into());
1674 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1675 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1676 assert!(params.is_empty());
1677 }
1678
1679 #[test]
1680 fn test_filter_in() {
1681 let filter = Filter::In("status".into(), vec!["active".into(), "pending".into()]);
1682 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1683 assert!(sql.contains("IN"));
1684 assert_eq!(params.len(), 2);
1685 }
1686
1687 #[test]
1688 fn test_filter_contains() {
1689 let filter = Filter::Contains("email".into(), "example".into());
1690 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1691 assert!(sql.contains("LIKE"));
1692 assert_eq!(params.len(), 1);
1693 if let FilterValue::String(s) = ¶ms[0] {
1694 assert!(s.contains("%example%"));
1695 }
1696 }
1697
1698 #[test]
1701 fn test_filter_value_is_null() {
1702 assert!(FilterValue::Null.is_null());
1703 assert!(!FilterValue::Bool(false).is_null());
1704 assert!(!FilterValue::Int(0).is_null());
1705 assert!(!FilterValue::Float(0.0).is_null());
1706 assert!(!FilterValue::String("".to_string()).is_null());
1707 }
1708
1709 #[test]
1710 fn test_filter_value_from_i64() {
1711 assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
1712 assert_eq!(FilterValue::from(-100i64), FilterValue::Int(-100));
1713 }
1714
1715 #[test]
1716 #[allow(clippy::approx_constant)]
1717 fn test_filter_value_from_f64() {
1718 assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1719 }
1720
1721 #[test]
1722 fn test_filter_value_from_string() {
1723 assert_eq!(
1724 FilterValue::from("hello".to_string()),
1725 FilterValue::String("hello".to_string())
1726 );
1727 }
1728
1729 #[test]
1730 fn test_filter_value_from_vec() {
1731 let values: Vec<i32> = vec![1, 2, 3];
1732 let filter_val: FilterValue = values.into();
1733 if let FilterValue::List(list) = filter_val {
1734 assert_eq!(list.len(), 3);
1735 assert_eq!(list[0], FilterValue::Int(1));
1736 assert_eq!(list[1], FilterValue::Int(2));
1737 assert_eq!(list[2], FilterValue::Int(3));
1738 } else {
1739 panic!("Expected List");
1740 }
1741 }
1742
1743 #[test]
1744 fn test_filter_value_from_option_some() {
1745 let val: FilterValue = Some(42i32).into();
1746 assert_eq!(val, FilterValue::Int(42));
1747 }
1748
1749 #[test]
1750 fn test_filter_value_from_option_none() {
1751 let val: FilterValue = Option::<i32>::None.into();
1752 assert_eq!(val, FilterValue::Null);
1753 }
1754
1755 #[test]
1758 fn test_scalar_filter_not() {
1759 let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1760 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1761 assert_eq!(sql, r#""name" != $1"#);
1762 assert_eq!(params.len(), 1);
1763 }
1764
1765 #[test]
1766 fn test_scalar_filter_in() {
1767 let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1768 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1769 assert!(sql.contains("IN"));
1770 assert_eq!(params.len(), 2);
1771 }
1772
1773 #[test]
1774 fn test_scalar_filter_not_in() {
1775 let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1776 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1777 assert!(sql.contains("NOT IN"));
1778 assert_eq!(params.len(), 1);
1779 }
1780
1781 #[test]
1782 fn test_scalar_filter_lt() {
1783 let filter = ScalarFilter::Lt(100i32).into_filter("price");
1784 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1785 assert_eq!(sql, r#""price" < $1"#);
1786 assert_eq!(params.len(), 1);
1787 }
1788
1789 #[test]
1790 fn test_scalar_filter_lte() {
1791 let filter = ScalarFilter::Lte(100i32).into_filter("price");
1792 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1793 assert_eq!(sql, r#""price" <= $1"#);
1794 assert_eq!(params.len(), 1);
1795 }
1796
1797 #[test]
1798 fn test_scalar_filter_gt() {
1799 let filter = ScalarFilter::Gt(0i32).into_filter("quantity");
1800 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1801 assert_eq!(sql, r#""quantity" > $1"#);
1802 assert_eq!(params.len(), 1);
1803 }
1804
1805 #[test]
1806 fn test_scalar_filter_gte() {
1807 let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1808 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1809 assert_eq!(sql, r#""quantity" >= $1"#);
1810 assert_eq!(params.len(), 1);
1811 }
1812
1813 #[test]
1814 fn test_scalar_filter_starts_with() {
1815 let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1816 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1817 assert!(sql.contains("LIKE"));
1818 assert_eq!(params.len(), 1);
1819 if let FilterValue::String(s) = ¶ms[0] {
1820 assert!(s.starts_with("prefix"));
1821 assert!(s.ends_with("%"));
1822 }
1823 }
1824
1825 #[test]
1826 fn test_scalar_filter_ends_with() {
1827 let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
1828 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1829 assert!(sql.contains("LIKE"));
1830 assert_eq!(params.len(), 1);
1831 if let FilterValue::String(s) = ¶ms[0] {
1832 assert!(s.starts_with("%"));
1833 assert!(s.ends_with("suffix"));
1834 }
1835 }
1836
1837 #[test]
1838 fn test_scalar_filter_is_null() {
1839 let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
1840 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1841 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1842 assert!(params.is_empty());
1843 }
1844
1845 #[test]
1846 fn test_scalar_filter_is_not_null() {
1847 let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
1848 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1849 assert_eq!(sql, r#""name" IS NOT NULL"#);
1850 assert!(params.is_empty());
1851 }
1852
1853 #[test]
1856 fn test_filter_none() {
1857 let filter = Filter::none();
1858 assert!(filter.is_none());
1859 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1860 assert_eq!(sql, "TRUE"); assert!(params.is_empty());
1862 }
1863
1864 #[test]
1865 fn test_filter_not_equals() {
1866 let filter = Filter::NotEquals("status".into(), "deleted".into());
1867 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1868 assert_eq!(sql, r#""status" != $1"#);
1869 assert_eq!(params.len(), 1);
1870 }
1871
1872 #[test]
1873 fn test_filter_lte() {
1874 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
1875 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1876 assert_eq!(sql, r#""price" <= $1"#);
1877 assert_eq!(params.len(), 1);
1878 }
1879
1880 #[test]
1881 fn test_filter_gte() {
1882 let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
1883 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1884 assert_eq!(sql, r#""quantity" >= $1"#);
1885 assert_eq!(params.len(), 1);
1886 }
1887
1888 #[test]
1889 fn test_filter_not_in() {
1890 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
1891 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1892 assert!(sql.contains("NOT IN"));
1893 assert_eq!(params.len(), 2);
1894 }
1895
1896 #[test]
1897 fn test_filter_starts_with() {
1898 let filter = Filter::StartsWith("email".into(), "admin".into());
1899 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1900 assert!(sql.contains("LIKE"));
1901 assert_eq!(params.len(), 1);
1902 }
1903
1904 #[test]
1905 fn test_filter_ends_with() {
1906 let filter = Filter::EndsWith("email".into(), "@example.com".into());
1907 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1908 assert!(sql.contains("LIKE"));
1909 assert_eq!(params.len(), 1);
1910 }
1911
1912 #[test]
1913 fn test_filter_is_not_null() {
1914 let filter = Filter::IsNotNull("name".into());
1915 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1916 assert_eq!(sql, r#""name" IS NOT NULL"#);
1917 assert!(params.is_empty());
1918 }
1919
1920 #[test]
1923 fn test_filter_and_empty() {
1924 let filter = Filter::and([]);
1925 assert!(filter.is_none());
1926 }
1927
1928 #[test]
1929 fn test_filter_and_single() {
1930 let f = Filter::Equals("name".into(), "Alice".into());
1931 let combined = Filter::and([f.clone()]);
1932 assert_eq!(combined, f);
1933 }
1934
1935 #[test]
1936 fn test_filter_and_with_none() {
1937 let f1 = Filter::Equals("name".into(), "Alice".into());
1938 let f2 = Filter::None;
1939 let combined = Filter::and([f1.clone(), f2]);
1940 assert_eq!(combined, f1);
1941 }
1942
1943 #[test]
1944 fn test_filter_or_empty() {
1945 let filter = Filter::or([]);
1946 assert!(filter.is_none());
1947 }
1948
1949 #[test]
1950 fn test_filter_or_single() {
1951 let f = Filter::Equals("status".into(), "active".into());
1952 let combined = Filter::or([f.clone()]);
1953 assert_eq!(combined, f);
1954 }
1955
1956 #[test]
1957 fn test_filter_or_with_none() {
1958 let f1 = Filter::Equals("status".into(), "active".into());
1959 let f2 = Filter::None;
1960 let combined = Filter::or([f1.clone(), f2]);
1961 assert_eq!(combined, f1);
1962 }
1963
1964 #[test]
1965 fn test_filter_not_none() {
1966 let filter = Filter::not(Filter::None);
1967 assert!(filter.is_none());
1968 }
1969
1970 #[test]
1971 fn test_filter_and_then() {
1972 let f1 = Filter::Equals("name".into(), "Alice".into());
1973 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1974 let combined = f1.and_then(f2);
1975
1976 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
1977 assert!(sql.contains("AND"));
1978 assert_eq!(params.len(), 2);
1979 }
1980
1981 #[test]
1982 fn test_filter_and_then_with_none_first() {
1983 let f1 = Filter::None;
1984 let f2 = Filter::Equals("name".into(), "Bob".into());
1985 let combined = f1.and_then(f2.clone());
1986 assert_eq!(combined, f2);
1987 }
1988
1989 #[test]
1990 fn test_filter_and_then_with_none_second() {
1991 let f1 = Filter::Equals("name".into(), "Alice".into());
1992 let f2 = Filter::None;
1993 let combined = f1.clone().and_then(f2);
1994 assert_eq!(combined, f1);
1995 }
1996
1997 #[test]
1998 fn test_filter_and_then_chained() {
1999 let f1 = Filter::Equals("a".into(), "1".into());
2000 let f2 = Filter::Equals("b".into(), "2".into());
2001 let f3 = Filter::Equals("c".into(), "3".into());
2002 let combined = f1.and_then(f2).and_then(f3);
2003
2004 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2005 assert!(sql.contains("AND"));
2006 assert_eq!(params.len(), 3);
2007 }
2008
2009 #[test]
2010 fn test_filter_or_else() {
2011 let f1 = Filter::Equals("status".into(), "active".into());
2012 let f2 = Filter::Equals("status".into(), "pending".into());
2013 let combined = f1.or_else(f2);
2014
2015 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
2016 assert!(sql.contains("OR"));
2017 }
2018
2019 #[test]
2020 fn test_filter_or_else_with_none_first() {
2021 let f1 = Filter::None;
2022 let f2 = Filter::Equals("name".into(), "Bob".into());
2023 let combined = f1.or_else(f2.clone());
2024 assert_eq!(combined, f2);
2025 }
2026
2027 #[test]
2028 fn test_filter_or_else_with_none_second() {
2029 let f1 = Filter::Equals("name".into(), "Alice".into());
2030 let f2 = Filter::None;
2031 let combined = f1.clone().or_else(f2);
2032 assert_eq!(combined, f1);
2033 }
2034
2035 #[test]
2038 fn test_filter_nested_and_or() {
2039 let f1 = Filter::Equals("status".into(), "active".into());
2040 let f2 = Filter::and([
2041 Filter::Gt("age".into(), FilterValue::Int(18)),
2042 Filter::Lt("age".into(), FilterValue::Int(65)),
2043 ]);
2044 let combined = Filter::and([f1, f2]);
2045
2046 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2047 assert!(sql.contains("AND"));
2048 assert_eq!(params.len(), 3);
2049 }
2050
2051 #[test]
2052 fn test_filter_nested_not() {
2053 let inner = Filter::and([
2054 Filter::Equals("status".into(), "deleted".into()),
2055 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2056 ]);
2057 let filter = Filter::not(inner);
2058
2059 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2060 assert!(sql.contains("NOT"));
2061 assert!(sql.contains("AND"));
2062 assert_eq!(params.len(), 2);
2063 }
2064
2065 #[test]
2066 fn test_filter_with_json_value() {
2067 let json_val = serde_json::json!({"key": "value"});
2068 let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
2069 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2070 assert_eq!(sql, r#""metadata" = $1"#);
2071 assert_eq!(params.len(), 1);
2072 }
2073
2074 #[test]
2075 fn test_filter_in_empty_list() {
2076 let filter = Filter::In("status".into(), vec![]);
2077 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2078 assert!(
2080 sql.contains("FALSE")
2081 || sql.contains("1=0")
2082 || sql.is_empty()
2083 || sql.contains("status")
2084 );
2085 assert!(params.is_empty());
2086 }
2087
2088 #[test]
2089 fn test_filter_with_null_value() {
2090 let filter = Filter::IsNull("deleted_at".into());
2092 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2093 assert!(sql.contains("deleted_at"));
2094 assert!(sql.contains("IS NULL"));
2095 assert!(params.is_empty());
2096 }
2097
2098 #[test]
2101 fn test_and_builder_basic() {
2102 let filter = Filter::and_builder(3)
2103 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2104 .push(Filter::Gt("score".into(), FilterValue::Int(100)))
2105 .push(Filter::IsNotNull("email".into()))
2106 .build();
2107
2108 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2109 assert!(sql.contains("AND"));
2110 assert_eq!(params.len(), 2); }
2112
2113 #[test]
2114 fn test_and_builder_empty() {
2115 let filter = Filter::and_builder(0).build();
2116 assert!(filter.is_none());
2117 }
2118
2119 #[test]
2120 fn test_and_builder_single() {
2121 let filter = Filter::and_builder(1)
2122 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2123 .build();
2124
2125 assert!(matches!(filter, Filter::Equals(_, _)));
2127 }
2128
2129 #[test]
2130 fn test_and_builder_filters_none() {
2131 let filter = Filter::and_builder(3)
2132 .push(Filter::None)
2133 .push(Filter::Equals("id".into(), FilterValue::Int(1)))
2134 .push(Filter::None)
2135 .build();
2136
2137 assert!(matches!(filter, Filter::Equals(_, _)));
2139 }
2140
2141 #[test]
2142 fn test_and_builder_push_if() {
2143 let include_deleted = false;
2144 let filter = Filter::and_builder(2)
2145 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2146 .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
2147 .build();
2148
2149 assert!(matches!(filter, Filter::Equals(_, _)));
2151 }
2152
2153 #[test]
2154 fn test_or_builder_basic() {
2155 let filter = Filter::or_builder(2)
2156 .push(Filter::Equals(
2157 "role".into(),
2158 FilterValue::String("admin".into()),
2159 ))
2160 .push(Filter::Equals(
2161 "role".into(),
2162 FilterValue::String("moderator".into()),
2163 ))
2164 .build();
2165
2166 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2167 assert!(sql.contains("OR"));
2168 }
2169
2170 #[test]
2171 fn test_or_builder_empty() {
2172 let filter = Filter::or_builder(0).build();
2173 assert!(filter.is_none());
2174 }
2175
2176 #[test]
2177 fn test_or_builder_single() {
2178 let filter = Filter::or_builder(1)
2179 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2180 .build();
2181
2182 assert!(matches!(filter, Filter::Equals(_, _)));
2184 }
2185
2186 #[test]
2187 fn test_fluent_builder_and() {
2188 let filter = Filter::builder()
2189 .eq("status", "active")
2190 .gt("age", 18)
2191 .is_not_null("email")
2192 .build_and();
2193
2194 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2195 assert!(sql.contains("AND"));
2196 assert_eq!(params.len(), 2);
2197 }
2198
2199 #[test]
2200 fn test_fluent_builder_or() {
2201 let filter = Filter::builder()
2202 .eq("role", "admin")
2203 .eq("role", "moderator")
2204 .build_or();
2205
2206 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2207 assert!(sql.contains("OR"));
2208 }
2209
2210 #[test]
2211 fn test_fluent_builder_with_capacity() {
2212 let filter = Filter::builder()
2213 .with_capacity(5)
2214 .eq("a", 1)
2215 .ne("b", 2)
2216 .lt("c", 3)
2217 .lte("d", 4)
2218 .gte("e", 5)
2219 .build_and();
2220
2221 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2222 assert!(sql.contains("AND"));
2223 assert_eq!(params.len(), 5);
2224 }
2225
2226 #[test]
2227 fn test_fluent_builder_string_operations() {
2228 let filter = Filter::builder()
2229 .contains("name", "john")
2230 .starts_with("email", "admin")
2231 .ends_with("domain", ".com")
2232 .build_and();
2233
2234 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2235 assert!(sql.contains("LIKE"));
2236 }
2237
2238 #[test]
2239 fn test_fluent_builder_null_operations() {
2240 let filter = Filter::builder()
2241 .is_null("deleted_at")
2242 .is_not_null("created_at")
2243 .build_and();
2244
2245 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2246 assert!(sql.contains("IS NULL"));
2247 assert!(sql.contains("IS NOT NULL"));
2248 }
2249
2250 #[test]
2251 fn test_fluent_builder_in_operations() {
2252 let filter = Filter::builder()
2253 .is_in("status", vec!["pending", "processing"])
2254 .not_in("role", vec!["banned", "suspended"])
2255 .build_and();
2256
2257 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2258 assert!(sql.contains("IN"));
2259 assert!(sql.contains("NOT IN"));
2260 }
2261
2262 #[test]
2263 fn test_fluent_builder_filter_if() {
2264 let include_archived = false;
2265 let filter = Filter::builder()
2266 .eq("active", true)
2267 .filter_if(
2268 include_archived,
2269 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2270 )
2271 .build_and();
2272
2273 assert!(matches!(filter, Filter::Equals(_, _)));
2275 }
2276
2277 #[test]
2278 fn test_fluent_builder_filter_if_some() {
2279 let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2280 let filter = Filter::builder()
2281 .eq("id", 1)
2282 .filter_if_some(maybe_status)
2283 .build_and();
2284
2285 assert!(matches!(filter, Filter::And(_)));
2286 }
2287
2288 #[test]
2289 fn test_and_builder_extend() {
2290 let extra_filters = vec![
2291 Filter::Gt("score".into(), FilterValue::Int(100)),
2292 Filter::Lt("score".into(), FilterValue::Int(1000)),
2293 ];
2294
2295 let filter = Filter::and_builder(3)
2296 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2297 .extend(extra_filters)
2298 .build();
2299
2300 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2301 assert!(sql.contains("AND"));
2302 assert_eq!(params.len(), 3);
2303 }
2304
2305 #[test]
2306 fn test_builder_len_and_is_empty() {
2307 let mut builder = AndFilterBuilder::new();
2308 assert!(builder.is_empty());
2309 assert_eq!(builder.len(), 0);
2310
2311 builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2312 assert!(!builder.is_empty());
2313 assert_eq!(builder.len(), 1);
2314 }
2315
2316 #[test]
2319 fn test_and2_both_valid() {
2320 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2321 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2322 let filter = Filter::and2(a, b);
2323
2324 assert!(matches!(filter, Filter::And(_)));
2325 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2326 assert!(sql.contains("AND"));
2327 assert_eq!(params.len(), 2);
2328 }
2329
2330 #[test]
2331 fn test_and2_first_none() {
2332 let a = Filter::None;
2333 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2334 let filter = Filter::and2(a, b.clone());
2335
2336 assert_eq!(filter, b);
2337 }
2338
2339 #[test]
2340 fn test_and2_second_none() {
2341 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2342 let b = Filter::None;
2343 let filter = Filter::and2(a.clone(), b);
2344
2345 assert_eq!(filter, a);
2346 }
2347
2348 #[test]
2349 fn test_and2_both_none() {
2350 let filter = Filter::and2(Filter::None, Filter::None);
2351 assert!(filter.is_none());
2352 }
2353
2354 #[test]
2355 fn test_or2_both_valid() {
2356 let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2357 let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2358 let filter = Filter::or2(a, b);
2359
2360 assert!(matches!(filter, Filter::Or(_)));
2361 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2362 assert!(sql.contains("OR"));
2363 }
2364
2365 #[test]
2366 fn test_or2_first_none() {
2367 let a = Filter::None;
2368 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2369 let filter = Filter::or2(a, b.clone());
2370
2371 assert_eq!(filter, b);
2372 }
2373
2374 #[test]
2375 fn test_or2_second_none() {
2376 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2377 let b = Filter::None;
2378 let filter = Filter::or2(a.clone(), b);
2379
2380 assert_eq!(filter, a);
2381 }
2382
2383 #[test]
2384 fn test_or2_both_none() {
2385 let filter = Filter::or2(Filter::None, Filter::None);
2386 assert!(filter.is_none());
2387 }
2388
2389 #[test]
2392 fn to_sql_quotes_column_names_against_injection() {
2393 use crate::dialect::{Mssql, Mysql, Postgres};
2394
2395 let filter = Filter::Equals(r#"id" OR 1=1--"#.into(), FilterValue::Int(1));
2397
2398 let (sql_pg, _) = filter.to_sql(0, &Postgres);
2399 assert!(
2400 sql_pg.starts_with(r#""id"" OR 1=1--" ="#),
2401 "postgres did not quote col; got: {sql_pg}"
2402 );
2403
2404 let (sql_my, _) = filter.to_sql(0, &Mysql);
2405 assert!(
2406 sql_my.starts_with(r#"`id" OR 1=1--` ="#),
2407 "mysql did not quote col; got: {sql_my}"
2408 );
2409
2410 let (sql_ms, _) = filter.to_sql(0, &Mssql);
2411 assert!(
2412 sql_ms.starts_with(r#"[id" OR 1=1--] ="#),
2413 "mssql did not quote col; got: {sql_ms}"
2414 );
2415 }
2416
2417 #[test]
2418 fn to_sql_quotes_in_list_column_names() {
2419 use crate::dialect::Postgres;
2420 let filter = Filter::In("id".into(), vec![FilterValue::Int(1), FilterValue::Int(2)]);
2421 let (sql, _) = filter.to_sql(0, &Postgres);
2422 assert!(
2423 sql.starts_with(r#""id" IN ("#),
2424 "expected quoted id on IN, got: {sql}"
2425 );
2426 }
2427
2428 #[test]
2429 fn to_sql_quotes_null_checks() {
2430 use crate::dialect::Postgres;
2431 let filter = Filter::IsNull("deleted_at".into());
2432 let (sql, _) = filter.to_sql(0, &Postgres);
2433 assert_eq!(sql, r#""deleted_at" IS NULL"#);
2434 }
2435
2436 #[test]
2437 fn to_sql_quotes_comparison_operators() {
2438 use crate::dialect::Postgres;
2439
2440 let filter = Filter::Lt("age".into(), FilterValue::Int(18));
2441 let (sql, _) = filter.to_sql(0, &Postgres);
2442 assert!(sql.starts_with(r#""age" < "#), "Lt not quoted: {sql}");
2443
2444 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
2445 let (sql, _) = filter.to_sql(0, &Postgres);
2446 assert!(sql.starts_with(r#""price" <= "#), "Lte not quoted: {sql}");
2447
2448 let filter = Filter::Gt("score".into(), FilterValue::Int(0));
2449 let (sql, _) = filter.to_sql(0, &Postgres);
2450 assert!(sql.starts_with(r#""score" > "#), "Gt not quoted: {sql}");
2451
2452 let filter = Filter::Gte("quantity".into(), FilterValue::Int(1));
2453 let (sql, _) = filter.to_sql(0, &Postgres);
2454 assert!(
2455 sql.starts_with(r#""quantity" >= "#),
2456 "Gte not quoted: {sql}"
2457 );
2458
2459 let filter = Filter::NotEquals("status".into(), "deleted".into());
2460 let (sql, _) = filter.to_sql(0, &Postgres);
2461 assert!(
2462 sql.starts_with(r#""status" != "#),
2463 "NotEquals not quoted: {sql}"
2464 );
2465 }
2466
2467 #[test]
2468 fn to_sql_quotes_like_operators() {
2469 use crate::dialect::Postgres;
2470
2471 let filter = Filter::Contains("email".into(), "example".into());
2472 let (sql, _) = filter.to_sql(0, &Postgres);
2473 assert!(
2474 sql.starts_with(r#""email" LIKE "#),
2475 "Contains not quoted: {sql}"
2476 );
2477
2478 let filter = Filter::StartsWith("name".into(), "admin".into());
2479 let (sql, _) = filter.to_sql(0, &Postgres);
2480 assert!(
2481 sql.starts_with(r#""name" LIKE "#),
2482 "StartsWith not quoted: {sql}"
2483 );
2484
2485 let filter = Filter::EndsWith("domain".into(), ".com".into());
2486 let (sql, _) = filter.to_sql(0, &Postgres);
2487 assert!(
2488 sql.starts_with(r#""domain" LIKE "#),
2489 "EndsWith not quoted: {sql}"
2490 );
2491 }
2492
2493 #[test]
2494 fn to_sql_quotes_not_in() {
2495 use crate::dialect::Postgres;
2496 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
2497 let (sql, _) = filter.to_sql(0, &Postgres);
2498 assert!(
2499 sql.starts_with(r#""status" NOT IN ("#),
2500 "NotIn not quoted: {sql}"
2501 );
2502 }
2503
2504 #[test]
2505 fn to_sql_quotes_is_not_null() {
2506 use crate::dialect::Postgres;
2507 let filter = Filter::IsNotNull("verified_at".into());
2508 let (sql, _) = filter.to_sql(0, &Postgres);
2509 assert_eq!(sql, r#""verified_at" IS NOT NULL"#);
2510 }
2511
2512 #[test]
2513 fn filter_value_from_u64_in_range() {
2514 assert_eq!(FilterValue::from(42u64), FilterValue::Int(42));
2515 assert_eq!(FilterValue::from(0u64), FilterValue::Int(0));
2516 let max_safe = i64::MAX as u64;
2517 assert_eq!(FilterValue::from(max_safe), FilterValue::Int(i64::MAX));
2518 }
2519
2520 #[test]
2521 #[should_panic(expected = "u64 value exceeds i64::MAX")]
2522 fn filter_value_from_u64_overflow_panics() {
2523 let _ = FilterValue::from(u64::MAX);
2524 }
2525
2526 #[test]
2527 fn filter_value_from_chrono_datetime_utc_rfc3339() {
2528 use chrono::{TimeZone, Utc};
2529 let dt = Utc.with_ymd_and_hms(2020, 1, 15, 10, 30, 45).unwrap();
2530 let fv = FilterValue::from(dt);
2531 assert_eq!(
2532 fv,
2533 FilterValue::String("2020-01-15T10:30:45.000000Z".to_string())
2534 );
2535 }
2536
2537 #[test]
2538 fn filter_value_from_chrono_naive_datetime_iso() {
2539 use chrono::NaiveDate;
2540 let dt = NaiveDate::from_ymd_opt(2020, 1, 15)
2541 .unwrap()
2542 .and_hms_opt(10, 30, 45)
2543 .unwrap();
2544 let fv = FilterValue::from(dt);
2545 assert_eq!(
2546 fv,
2547 FilterValue::String("2020-01-15T10:30:45.000000".to_string())
2548 );
2549 }
2550
2551 #[test]
2552 fn filter_value_from_chrono_naive_date() {
2553 use chrono::NaiveDate;
2554 let d = NaiveDate::from_ymd_opt(2020, 1, 15).unwrap();
2555 assert_eq!(
2556 FilterValue::from(d),
2557 FilterValue::String("2020-01-15".to_string())
2558 );
2559 }
2560
2561 #[test]
2562 fn filter_value_from_chrono_naive_time() {
2563 use chrono::NaiveTime;
2564 let t = NaiveTime::from_hms_opt(10, 30, 45).unwrap();
2565 assert_eq!(
2566 FilterValue::from(t),
2567 FilterValue::String("10:30:45.000000".to_string())
2568 );
2569 }
2570
2571 #[test]
2578 fn filter_value_from_uuid_is_lowercase_hyphenated() {
2579 use uuid::Uuid;
2584 let u = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
2585 match FilterValue::from(u) {
2586 FilterValue::String(ref s) => {
2587 assert_eq!(s, "550e8400-e29b-41d4-a716-446655440000");
2588 assert_eq!(s, &u.to_string());
2589 }
2590 other => panic!("expected FilterValue::String, got {other:?}"),
2591 }
2592 }
2593
2594 #[test]
2595 fn filter_value_from_uuid_nil_round_trips() {
2596 use uuid::Uuid;
2597 let u = Uuid::nil();
2598 assert_eq!(
2599 FilterValue::from(u),
2600 FilterValue::String("00000000-0000-0000-0000-000000000000".to_string())
2601 );
2602 }
2603
2604 #[test]
2605 fn filter_value_from_decimal_uses_to_string_not_f64() {
2606 use rust_decimal::Decimal;
2610 use std::str::FromStr;
2611 let d = Decimal::from_str("3.14").unwrap();
2612 assert_eq!(
2613 FilterValue::from(d),
2614 FilterValue::String("3.14".to_string())
2615 );
2616 }
2617
2618 #[test]
2619 fn filter_value_from_decimal_high_precision_preserved() {
2620 use rust_decimal::Decimal;
2621 use std::str::FromStr;
2622 let d = Decimal::from_str("1234567890.1234567890").unwrap();
2624 match FilterValue::from(d) {
2625 FilterValue::String(ref s) => {
2626 assert_eq!(s, "1234567890.1234567890");
2627 }
2628 other => panic!("expected FilterValue::String, got {other:?}"),
2629 }
2630 }
2631
2632 #[test]
2633 fn filter_value_from_serde_json_value_keeps_json_variant() {
2634 let v = serde_json::json!({"key": "value", "nested": [1, 2, 3]});
2635 match FilterValue::from(v.clone()) {
2636 FilterValue::Json(inner) => {
2637 assert_eq!(inner, v);
2638 }
2639 other => panic!("expected FilterValue::Json, got {other:?}"),
2640 }
2641 }
2642
2643 #[test]
2644 fn filter_value_from_serde_json_null_keeps_json_variant() {
2645 let v = serde_json::Value::Null;
2650 match FilterValue::from(v) {
2651 FilterValue::Json(serde_json::Value::Null) => {}
2652 other => panic!("expected FilterValue::Json(Null), got {other:?}"),
2653 }
2654 }
2655
2656 #[test]
2657 fn filter_value_from_option_none_maps_to_null() {
2658 let none_i32: Option<i32> = None;
2661 assert_eq!(FilterValue::from(none_i32), FilterValue::Null);
2662 let none_string: Option<String> = None;
2663 assert_eq!(FilterValue::from(none_string), FilterValue::Null);
2664 }
2665
2666 #[test]
2667 fn filter_value_from_signed_integer_extremes() {
2668 assert_eq!(FilterValue::from(i8::MIN), FilterValue::Int(i8::MIN as i64));
2671 assert_eq!(FilterValue::from(i8::MAX), FilterValue::Int(i8::MAX as i64));
2672 assert_eq!(
2673 FilterValue::from(i16::MIN),
2674 FilterValue::Int(i16::MIN as i64)
2675 );
2676 assert_eq!(
2677 FilterValue::from(i16::MAX),
2678 FilterValue::Int(i16::MAX as i64)
2679 );
2680 }
2681
2682 #[test]
2683 fn filter_value_from_unsigned_integer_extremes() {
2684 assert_eq!(FilterValue::from(u8::MAX), FilterValue::Int(u8::MAX as i64));
2687 assert_eq!(
2688 FilterValue::from(u16::MAX),
2689 FilterValue::Int(u16::MAX as i64)
2690 );
2691 assert_eq!(
2692 FilterValue::from(u32::MAX),
2693 FilterValue::Int(u32::MAX as i64)
2694 );
2695 assert_eq!(FilterValue::from(u32::MAX), FilterValue::Int(4_294_967_295));
2697 }
2698
2699 #[test]
2700 fn filter_value_from_f32_widens_to_f64() {
2701 let v: f32 = 1.5;
2706 assert_eq!(FilterValue::from(v), FilterValue::Float(1.5));
2707 }
2708
2709 #[test]
2716 fn to_filter_value_option_some_some() {
2717 let v: Option<i32> = Some(42);
2718 assert_eq!(v.to_filter_value(), FilterValue::Int(42));
2719 }
2720
2721 #[test]
2722 fn to_filter_value_option_none_is_null() {
2723 let v: Option<i32> = None;
2724 assert_eq!(v.to_filter_value(), FilterValue::Null);
2725 }
2726
2727 #[test]
2728 fn to_filter_value_uuid_is_string() {
2729 let id = uuid::Uuid::nil();
2730 assert_eq!(id.to_filter_value(), FilterValue::String(id.to_string()));
2731 }
2732
2733 #[test]
2734 fn to_filter_value_bool_is_bool() {
2735 assert_eq!(true.to_filter_value(), FilterValue::Bool(true));
2736 }
2737}