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 ToFilterValue for Vec<f32> {
458 fn to_filter_value(&self) -> FilterValue {
459 FilterValue::List(self.iter().map(|f| FilterValue::Float(*f as f64)).collect())
463 }
464}
465impl<T: ToFilterValue> ToFilterValue for Option<T> {
466 fn to_filter_value(&self) -> FilterValue {
467 self.as_ref()
468 .map(T::to_filter_value)
469 .unwrap_or(FilterValue::Null)
470 }
471}
472
473#[derive(Debug, Clone, PartialEq)]
475pub enum ScalarFilter<T> {
476 Equals(T),
478 Not(Box<T>),
480 In(Vec<T>),
482 NotIn(Vec<T>),
484 Lt(T),
486 Lte(T),
488 Gt(T),
490 Gte(T),
492 Contains(T),
494 StartsWith(T),
496 EndsWith(T),
498 IsNull,
500 IsNotNull,
502}
503
504impl<T: Into<FilterValue>> ScalarFilter<T> {
505 pub fn into_filter(self, column: impl Into<FieldName>) -> Filter {
510 let column = column.into();
511 match self {
512 Self::Equals(v) => Filter::Equals(column, v.into()),
513 Self::Not(v) => Filter::NotEquals(column, (*v).into()),
514 Self::In(values) => Filter::In(column, values.into_iter().map(Into::into).collect()),
515 Self::NotIn(values) => {
516 Filter::NotIn(column, values.into_iter().map(Into::into).collect())
517 }
518 Self::Lt(v) => Filter::Lt(column, v.into()),
519 Self::Lte(v) => Filter::Lte(column, v.into()),
520 Self::Gt(v) => Filter::Gt(column, v.into()),
521 Self::Gte(v) => Filter::Gte(column, v.into()),
522 Self::Contains(v) => Filter::Contains(column, v.into()),
523 Self::StartsWith(v) => Filter::StartsWith(column, v.into()),
524 Self::EndsWith(v) => Filter::EndsWith(column, v.into()),
525 Self::IsNull => Filter::IsNull(column),
526 Self::IsNotNull => Filter::IsNotNull(column),
527 }
528 }
529}
530
531#[derive(Debug, Clone, PartialEq)]
552#[repr(C)] #[derive(Default)]
554pub enum Filter {
555 #[default]
557 None,
558
559 Equals(FieldName, FilterValue),
561 NotEquals(FieldName, FilterValue),
563
564 Lt(FieldName, FilterValue),
566 Lte(FieldName, FilterValue),
568 Gt(FieldName, FilterValue),
570 Gte(FieldName, FilterValue),
572
573 In(FieldName, ValueList),
575 NotIn(FieldName, ValueList),
577
578 Contains(FieldName, FilterValue),
580 StartsWith(FieldName, FilterValue),
582 EndsWith(FieldName, FilterValue),
584
585 IsNull(FieldName),
587 IsNotNull(FieldName),
589
590 And(Box<[Filter]>),
595 Or(Box<[Filter]>),
600 Not(Box<Filter>),
602}
603
604impl Filter {
605 #[inline(always)]
607 pub fn none() -> Self {
608 Self::None
609 }
610
611 #[inline(always)]
613 pub fn is_none(&self) -> bool {
614 matches!(self, Self::None)
615 }
616
617 #[inline]
623 pub fn and(filters: impl IntoIterator<Item = Filter>) -> Self {
624 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
625 let count = filters.len();
626 let result = match count {
627 0 => Self::None,
628 1 => filters.into_iter().next().unwrap(),
629 _ => Self::And(filters.into_boxed_slice()),
630 };
631 debug!(count, "Filter::and() created");
632 result
633 }
634
635 #[inline(always)]
639 pub fn and2(a: Filter, b: Filter) -> Self {
640 match (a.is_none(), b.is_none()) {
641 (true, true) => Self::None,
642 (true, false) => b,
643 (false, true) => a,
644 (false, false) => Self::And(Box::new([a, b])),
645 }
646 }
647
648 #[inline]
654 pub fn or(filters: impl IntoIterator<Item = Filter>) -> Self {
655 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
656 let count = filters.len();
657 let result = match count {
658 0 => Self::None,
659 1 => filters.into_iter().next().unwrap(),
660 _ => Self::Or(filters.into_boxed_slice()),
661 };
662 debug!(count, "Filter::or() created");
663 result
664 }
665
666 #[inline(always)]
670 pub fn or2(a: Filter, b: Filter) -> Self {
671 match (a.is_none(), b.is_none()) {
672 (true, true) => Self::None,
673 (true, false) => b,
674 (false, true) => a,
675 (false, false) => Self::Or(Box::new([a, b])),
676 }
677 }
678
679 #[inline(always)]
700 pub fn and_n<const N: usize>(filters: [Filter; N]) -> Self {
701 Self::And(Box::new(filters))
703 }
704
705 #[inline(always)]
710 pub fn or_n<const N: usize>(filters: [Filter; N]) -> Self {
711 Self::Or(Box::new(filters))
712 }
713
714 #[inline(always)]
716 pub fn and3(a: Filter, b: Filter, c: Filter) -> Self {
717 Self::And(Box::new([a, b, c]))
718 }
719
720 #[inline(always)]
722 pub fn and4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
723 Self::And(Box::new([a, b, c, d]))
724 }
725
726 #[inline(always)]
728 pub fn and5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
729 Self::And(Box::new([a, b, c, d, e]))
730 }
731
732 #[inline(always)]
734 pub fn or3(a: Filter, b: Filter, c: Filter) -> Self {
735 Self::Or(Box::new([a, b, c]))
736 }
737
738 #[inline(always)]
740 pub fn or4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
741 Self::Or(Box::new([a, b, c, d]))
742 }
743
744 #[inline(always)]
746 pub fn or5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
747 Self::Or(Box::new([a, b, c, d, e]))
748 }
749
750 #[inline]
759 pub fn in_i64(field: impl Into<FieldName>, values: impl IntoIterator<Item = i64>) -> Self {
760 let list: ValueList = values.into_iter().map(FilterValue::Int).collect();
761 Self::In(field.into(), list)
762 }
763
764 #[inline]
766 pub fn in_i32(field: impl Into<FieldName>, values: impl IntoIterator<Item = i32>) -> Self {
767 let list: ValueList = values
768 .into_iter()
769 .map(|v| FilterValue::Int(v as i64))
770 .collect();
771 Self::In(field.into(), list)
772 }
773
774 #[inline]
776 pub fn in_strings(
777 field: impl Into<FieldName>,
778 values: impl IntoIterator<Item = String>,
779 ) -> Self {
780 let list: ValueList = values.into_iter().map(FilterValue::String).collect();
781 Self::In(field.into(), list)
782 }
783
784 #[inline]
788 pub fn in_values(field: impl Into<FieldName>, values: ValueList) -> Self {
789 Self::In(field.into(), values)
790 }
791
792 #[inline]
796 pub fn in_range(field: impl Into<FieldName>, range: std::ops::Range<i64>) -> Self {
797 let list: ValueList = range.map(FilterValue::Int).collect();
798 Self::In(field.into(), list)
799 }
800
801 #[inline(always)]
806 pub fn in_i64_slice(field: impl Into<FieldName>, values: &[i64]) -> Self {
807 let mut list = Vec::with_capacity(values.len());
808 for &v in values {
809 list.push(FilterValue::Int(v));
810 }
811 Self::In(field.into(), list)
812 }
813
814 #[inline(always)]
816 pub fn in_i32_slice(field: impl Into<FieldName>, values: &[i32]) -> Self {
817 let mut list = Vec::with_capacity(values.len());
818 for &v in values {
819 list.push(FilterValue::Int(v as i64));
820 }
821 Self::In(field.into(), list)
822 }
823
824 #[inline(always)]
826 pub fn in_str_slice(field: impl Into<FieldName>, values: &[&str]) -> Self {
827 let mut list = Vec::with_capacity(values.len());
828 for &v in values {
829 list.push(FilterValue::String(v.to_string()));
830 }
831 Self::In(field.into(), list)
832 }
833
834 #[inline]
836 #[allow(clippy::should_implement_trait)]
837 pub fn not(filter: Filter) -> Self {
838 if filter.is_none() {
839 return Self::None;
840 }
841 Self::Not(Box::new(filter))
842 }
843
844 #[inline]
858 pub fn in_slice<T: Into<FilterValue> + Clone>(
859 field: impl Into<FieldName>,
860 values: &[T],
861 ) -> Self {
862 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
863 Self::In(field.into(), list)
864 }
865
866 #[inline]
877 pub fn not_in_slice<T: Into<FilterValue> + Clone>(
878 field: impl Into<FieldName>,
879 values: &[T],
880 ) -> Self {
881 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
882 Self::NotIn(field.into(), list)
883 }
884
885 #[inline]
897 pub fn in_array<T: Into<FilterValue>, const N: usize>(
898 field: impl Into<FieldName>,
899 values: [T; N],
900 ) -> Self {
901 let list: ValueList = values.into_iter().map(Into::into).collect();
902 Self::In(field.into(), list)
903 }
904
905 #[inline]
907 pub fn not_in_array<T: Into<FilterValue>, const N: usize>(
908 field: impl Into<FieldName>,
909 values: [T; N],
910 ) -> Self {
911 let list: ValueList = values.into_iter().map(Into::into).collect();
912 Self::NotIn(field.into(), list)
913 }
914
915 pub fn and_then(self, other: Filter) -> Self {
917 if self.is_none() {
918 return other;
919 }
920 if other.is_none() {
921 return self;
922 }
923 match self {
924 Self::And(filters) => {
925 let mut vec: Vec<_> = filters.into_vec();
927 vec.push(other);
928 Self::And(vec.into_boxed_slice())
929 }
930 _ => Self::And(Box::new([self, other])),
931 }
932 }
933
934 pub fn or_else(self, other: Filter) -> Self {
936 if self.is_none() {
937 return other;
938 }
939 if other.is_none() {
940 return self;
941 }
942 match self {
943 Self::Or(filters) => {
944 let mut vec: Vec<_> = filters.into_vec();
946 vec.push(other);
947 Self::Or(vec.into_boxed_slice())
948 }
949 _ => Self::Or(Box::new([self, other])),
950 }
951 }
952
953 pub fn to_sql(
956 &self,
957 param_offset: usize,
958 dialect: &dyn crate::dialect::SqlDialect,
959 ) -> (String, Vec<FilterValue>) {
960 let mut params = Vec::new();
961 let sql = self.to_sql_with_params(param_offset, &mut params, dialect);
962 (sql, params)
963 }
964
965 fn to_sql_with_params(
966 &self,
967 mut param_idx: usize,
968 params: &mut Vec<FilterValue>,
969 dialect: &dyn crate::dialect::SqlDialect,
970 ) -> String {
971 match self {
972 Self::None => "TRUE".to_string(),
973
974 Self::Equals(col, val) => {
975 let c = dialect.quote_ident(col);
976 if val.is_null() {
977 format!("{} IS NULL", c)
978 } else {
979 params.push(val.clone());
980 param_idx += params.len();
981 format!("{} = {}", c, dialect.placeholder(param_idx))
982 }
983 }
984 Self::NotEquals(col, val) => {
985 let c = dialect.quote_ident(col);
986 if val.is_null() {
987 format!("{} IS NOT NULL", c)
988 } else {
989 params.push(val.clone());
990 param_idx += params.len();
991 format!("{} != {}", c, dialect.placeholder(param_idx))
992 }
993 }
994
995 Self::Lt(col, val) => {
996 let c = dialect.quote_ident(col);
997 params.push(val.clone());
998 param_idx += params.len();
999 format!("{} < {}", c, dialect.placeholder(param_idx))
1000 }
1001 Self::Lte(col, val) => {
1002 let c = dialect.quote_ident(col);
1003 params.push(val.clone());
1004 param_idx += params.len();
1005 format!("{} <= {}", c, dialect.placeholder(param_idx))
1006 }
1007 Self::Gt(col, val) => {
1008 let c = dialect.quote_ident(col);
1009 params.push(val.clone());
1010 param_idx += params.len();
1011 format!("{} > {}", c, dialect.placeholder(param_idx))
1012 }
1013 Self::Gte(col, val) => {
1014 let c = dialect.quote_ident(col);
1015 params.push(val.clone());
1016 param_idx += params.len();
1017 format!("{} >= {}", c, dialect.placeholder(param_idx))
1018 }
1019
1020 Self::In(col, values) => {
1021 if values.is_empty() {
1022 return "FALSE".to_string();
1023 }
1024 let c = dialect.quote_ident(col);
1025 let placeholders: Vec<_> = values
1026 .iter()
1027 .map(|v| {
1028 params.push(v.clone());
1029 param_idx += params.len();
1030 dialect.placeholder(param_idx)
1031 })
1032 .collect();
1033 format!("{} IN ({})", c, placeholders.join(", "))
1034 }
1035 Self::NotIn(col, values) => {
1036 if values.is_empty() {
1037 return "TRUE".to_string();
1038 }
1039 let c = dialect.quote_ident(col);
1040 let placeholders: Vec<_> = values
1041 .iter()
1042 .map(|v| {
1043 params.push(v.clone());
1044 param_idx += params.len();
1045 dialect.placeholder(param_idx)
1046 })
1047 .collect();
1048 format!("{} NOT IN ({})", c, placeholders.join(", "))
1049 }
1050
1051 Self::Contains(col, val) => {
1052 let c = dialect.quote_ident(col);
1053 if let FilterValue::String(s) = val {
1054 params.push(FilterValue::String(format!("%{}%", s)));
1055 } else {
1056 params.push(val.clone());
1057 }
1058 param_idx += params.len();
1059 format!("{} LIKE {}", c, dialect.placeholder(param_idx))
1060 }
1061 Self::StartsWith(col, val) => {
1062 let c = dialect.quote_ident(col);
1063 if let FilterValue::String(s) = val {
1064 params.push(FilterValue::String(format!("{}%", s)));
1065 } else {
1066 params.push(val.clone());
1067 }
1068 param_idx += params.len();
1069 format!("{} LIKE {}", c, dialect.placeholder(param_idx))
1070 }
1071 Self::EndsWith(col, val) => {
1072 let c = dialect.quote_ident(col);
1073 if let FilterValue::String(s) = val {
1074 params.push(FilterValue::String(format!("%{}", s)));
1075 } else {
1076 params.push(val.clone());
1077 }
1078 param_idx += params.len();
1079 format!("{} LIKE {}", c, dialect.placeholder(param_idx))
1080 }
1081
1082 Self::IsNull(col) => {
1083 let c = dialect.quote_ident(col);
1084 format!("{} IS NULL", c)
1085 }
1086 Self::IsNotNull(col) => {
1087 let c = dialect.quote_ident(col);
1088 format!("{} IS NOT NULL", c)
1089 }
1090
1091 Self::And(filters) => {
1092 if filters.is_empty() {
1093 return "TRUE".to_string();
1094 }
1095 let parts: Vec<_> = filters
1096 .iter()
1097 .map(|f| f.to_sql_with_params(param_idx + params.len(), params, dialect))
1098 .collect();
1099 format!("({})", parts.join(" AND "))
1100 }
1101 Self::Or(filters) => {
1102 if filters.is_empty() {
1103 return "FALSE".to_string();
1104 }
1105 let parts: Vec<_> = filters
1106 .iter()
1107 .map(|f| f.to_sql_with_params(param_idx + params.len(), params, dialect))
1108 .collect();
1109 format!("({})", parts.join(" OR "))
1110 }
1111 Self::Not(filter) => {
1112 let inner = filter.to_sql_with_params(param_idx, params, dialect);
1113 format!("NOT ({})", inner)
1114 }
1115 }
1116 }
1117
1118 #[inline]
1136 pub fn and_builder(capacity: usize) -> AndFilterBuilder {
1137 AndFilterBuilder::with_capacity(capacity)
1138 }
1139
1140 #[inline]
1157 pub fn or_builder(capacity: usize) -> OrFilterBuilder {
1158 OrFilterBuilder::with_capacity(capacity)
1159 }
1160
1161 #[inline]
1177 pub fn builder() -> FluentFilterBuilder {
1178 FluentFilterBuilder::new()
1179 }
1180}
1181
1182#[derive(Debug, Clone)]
1186pub struct AndFilterBuilder {
1187 filters: Vec<Filter>,
1188}
1189
1190impl AndFilterBuilder {
1191 #[inline]
1193 pub fn new() -> Self {
1194 Self {
1195 filters: Vec::new(),
1196 }
1197 }
1198
1199 #[inline]
1201 pub fn with_capacity(capacity: usize) -> Self {
1202 Self {
1203 filters: Vec::with_capacity(capacity),
1204 }
1205 }
1206
1207 #[inline]
1209 pub fn push(mut self, filter: Filter) -> Self {
1210 if !filter.is_none() {
1211 self.filters.push(filter);
1212 }
1213 self
1214 }
1215
1216 #[inline]
1218 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1219 self.filters
1220 .extend(filters.into_iter().filter(|f| !f.is_none()));
1221 self
1222 }
1223
1224 #[inline]
1226 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1227 if condition { self.push(filter) } else { self }
1228 }
1229
1230 #[inline]
1232 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1233 where
1234 F: Into<Filter>,
1235 {
1236 match opt {
1237 Some(f) => self.push(f.into()),
1238 None => self,
1239 }
1240 }
1241
1242 #[inline]
1244 pub fn build(self) -> Filter {
1245 match self.filters.len() {
1246 0 => Filter::None,
1247 1 => self.filters.into_iter().next().unwrap(),
1248 _ => Filter::And(self.filters.into_boxed_slice()),
1249 }
1250 }
1251
1252 #[inline]
1254 pub fn len(&self) -> usize {
1255 self.filters.len()
1256 }
1257
1258 #[inline]
1260 pub fn is_empty(&self) -> bool {
1261 self.filters.is_empty()
1262 }
1263}
1264
1265impl Default for AndFilterBuilder {
1266 fn default() -> Self {
1267 Self::new()
1268 }
1269}
1270
1271#[derive(Debug, Clone)]
1275pub struct OrFilterBuilder {
1276 filters: Vec<Filter>,
1277}
1278
1279impl OrFilterBuilder {
1280 #[inline]
1282 pub fn new() -> Self {
1283 Self {
1284 filters: Vec::new(),
1285 }
1286 }
1287
1288 #[inline]
1290 pub fn with_capacity(capacity: usize) -> Self {
1291 Self {
1292 filters: Vec::with_capacity(capacity),
1293 }
1294 }
1295
1296 #[inline]
1298 pub fn push(mut self, filter: Filter) -> Self {
1299 if !filter.is_none() {
1300 self.filters.push(filter);
1301 }
1302 self
1303 }
1304
1305 #[inline]
1307 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1308 self.filters
1309 .extend(filters.into_iter().filter(|f| !f.is_none()));
1310 self
1311 }
1312
1313 #[inline]
1315 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1316 if condition { self.push(filter) } else { self }
1317 }
1318
1319 #[inline]
1321 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1322 where
1323 F: Into<Filter>,
1324 {
1325 match opt {
1326 Some(f) => self.push(f.into()),
1327 None => self,
1328 }
1329 }
1330
1331 #[inline]
1333 pub fn build(self) -> Filter {
1334 match self.filters.len() {
1335 0 => Filter::None,
1336 1 => self.filters.into_iter().next().unwrap(),
1337 _ => Filter::Or(self.filters.into_boxed_slice()),
1338 }
1339 }
1340
1341 #[inline]
1343 pub fn len(&self) -> usize {
1344 self.filters.len()
1345 }
1346
1347 #[inline]
1349 pub fn is_empty(&self) -> bool {
1350 self.filters.is_empty()
1351 }
1352}
1353
1354impl Default for OrFilterBuilder {
1355 fn default() -> Self {
1356 Self::new()
1357 }
1358}
1359
1360#[derive(Debug, Clone)]
1385pub struct FluentFilterBuilder {
1386 filters: Vec<Filter>,
1387}
1388
1389impl FluentFilterBuilder {
1390 #[inline]
1392 pub fn new() -> Self {
1393 Self {
1394 filters: Vec::new(),
1395 }
1396 }
1397
1398 #[inline]
1400 pub fn with_capacity(mut self, capacity: usize) -> Self {
1401 self.filters.reserve(capacity);
1402 self
1403 }
1404
1405 #[inline]
1407 pub fn eq<F, V>(mut self, field: F, value: V) -> Self
1408 where
1409 F: Into<FieldName>,
1410 V: Into<FilterValue>,
1411 {
1412 self.filters
1413 .push(Filter::Equals(field.into(), value.into()));
1414 self
1415 }
1416
1417 #[inline]
1419 pub fn ne<F, V>(mut self, field: F, value: V) -> Self
1420 where
1421 F: Into<FieldName>,
1422 V: Into<FilterValue>,
1423 {
1424 self.filters
1425 .push(Filter::NotEquals(field.into(), value.into()));
1426 self
1427 }
1428
1429 #[inline]
1431 pub fn lt<F, V>(mut self, field: F, value: V) -> Self
1432 where
1433 F: Into<FieldName>,
1434 V: Into<FilterValue>,
1435 {
1436 self.filters.push(Filter::Lt(field.into(), value.into()));
1437 self
1438 }
1439
1440 #[inline]
1442 pub fn lte<F, V>(mut self, field: F, value: V) -> Self
1443 where
1444 F: Into<FieldName>,
1445 V: Into<FilterValue>,
1446 {
1447 self.filters.push(Filter::Lte(field.into(), value.into()));
1448 self
1449 }
1450
1451 #[inline]
1453 pub fn gt<F, V>(mut self, field: F, value: V) -> Self
1454 where
1455 F: Into<FieldName>,
1456 V: Into<FilterValue>,
1457 {
1458 self.filters.push(Filter::Gt(field.into(), value.into()));
1459 self
1460 }
1461
1462 #[inline]
1464 pub fn gte<F, V>(mut self, field: F, value: V) -> Self
1465 where
1466 F: Into<FieldName>,
1467 V: Into<FilterValue>,
1468 {
1469 self.filters.push(Filter::Gte(field.into(), value.into()));
1470 self
1471 }
1472
1473 #[inline]
1475 pub fn is_in<F, I, V>(mut self, field: F, values: I) -> Self
1476 where
1477 F: Into<FieldName>,
1478 I: IntoIterator<Item = V>,
1479 V: Into<FilterValue>,
1480 {
1481 self.filters.push(Filter::In(
1482 field.into(),
1483 values.into_iter().map(Into::into).collect(),
1484 ));
1485 self
1486 }
1487
1488 #[inline]
1490 pub fn not_in<F, I, V>(mut self, field: F, values: I) -> Self
1491 where
1492 F: Into<FieldName>,
1493 I: IntoIterator<Item = V>,
1494 V: Into<FilterValue>,
1495 {
1496 self.filters.push(Filter::NotIn(
1497 field.into(),
1498 values.into_iter().map(Into::into).collect(),
1499 ));
1500 self
1501 }
1502
1503 #[inline]
1505 pub fn contains<F, V>(mut self, field: F, value: V) -> Self
1506 where
1507 F: Into<FieldName>,
1508 V: Into<FilterValue>,
1509 {
1510 self.filters
1511 .push(Filter::Contains(field.into(), value.into()));
1512 self
1513 }
1514
1515 #[inline]
1517 pub fn starts_with<F, V>(mut self, field: F, value: V) -> Self
1518 where
1519 F: Into<FieldName>,
1520 V: Into<FilterValue>,
1521 {
1522 self.filters
1523 .push(Filter::StartsWith(field.into(), value.into()));
1524 self
1525 }
1526
1527 #[inline]
1529 pub fn ends_with<F, V>(mut self, field: F, value: V) -> Self
1530 where
1531 F: Into<FieldName>,
1532 V: Into<FilterValue>,
1533 {
1534 self.filters
1535 .push(Filter::EndsWith(field.into(), value.into()));
1536 self
1537 }
1538
1539 #[inline]
1541 pub fn is_null<F>(mut self, field: F) -> Self
1542 where
1543 F: Into<FieldName>,
1544 {
1545 self.filters.push(Filter::IsNull(field.into()));
1546 self
1547 }
1548
1549 #[inline]
1551 pub fn is_not_null<F>(mut self, field: F) -> Self
1552 where
1553 F: Into<FieldName>,
1554 {
1555 self.filters.push(Filter::IsNotNull(field.into()));
1556 self
1557 }
1558
1559 #[inline]
1561 pub fn filter(mut self, filter: Filter) -> Self {
1562 if !filter.is_none() {
1563 self.filters.push(filter);
1564 }
1565 self
1566 }
1567
1568 #[inline]
1570 pub fn filter_if(self, condition: bool, filter: Filter) -> Self {
1571 if condition { self.filter(filter) } else { self }
1572 }
1573
1574 #[inline]
1576 pub fn filter_if_some<F>(self, opt: Option<F>) -> Self
1577 where
1578 F: Into<Filter>,
1579 {
1580 match opt {
1581 Some(f) => self.filter(f.into()),
1582 None => self,
1583 }
1584 }
1585
1586 #[inline]
1588 pub fn build_and(self) -> Filter {
1589 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1590 match filters.len() {
1591 0 => Filter::None,
1592 1 => filters.into_iter().next().unwrap(),
1593 _ => Filter::And(filters.into_boxed_slice()),
1594 }
1595 }
1596
1597 #[inline]
1599 pub fn build_or(self) -> Filter {
1600 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1601 match filters.len() {
1602 0 => Filter::None,
1603 1 => filters.into_iter().next().unwrap(),
1604 _ => Filter::Or(filters.into_boxed_slice()),
1605 }
1606 }
1607
1608 #[inline]
1610 pub fn len(&self) -> usize {
1611 self.filters.len()
1612 }
1613
1614 #[inline]
1616 pub fn is_empty(&self) -> bool {
1617 self.filters.is_empty()
1618 }
1619}
1620
1621impl Default for FluentFilterBuilder {
1622 fn default() -> Self {
1623 Self::new()
1624 }
1625}
1626
1627#[cfg(test)]
1628mod tests {
1629 use super::*;
1630
1631 #[test]
1632 fn test_filter_value_from() {
1633 assert_eq!(FilterValue::from(42i32), FilterValue::Int(42));
1634 assert_eq!(
1635 FilterValue::from("hello"),
1636 FilterValue::String("hello".to_string())
1637 );
1638 assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
1639 }
1640
1641 #[test]
1642 fn test_scalar_filter_equals() {
1643 let filter = ScalarFilter::Equals("test@example.com".to_string()).into_filter("email");
1644
1645 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1646 assert_eq!(sql, r#""email" = $1"#);
1647 assert_eq!(params.len(), 1);
1648 }
1649
1650 #[test]
1651 fn test_filter_and() {
1652 let f1 = Filter::Equals("name".into(), "Alice".into());
1653 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1654 let combined = Filter::and([f1, f2]);
1655
1656 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
1657 assert!(sql.contains("AND"));
1658 assert_eq!(params.len(), 2);
1659 }
1660
1661 #[test]
1662 fn test_filter_or() {
1663 let f1 = Filter::Equals("status".into(), "active".into());
1664 let f2 = Filter::Equals("status".into(), "pending".into());
1665 let combined = Filter::or([f1, f2]);
1666
1667 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
1668 assert!(sql.contains("OR"));
1669 }
1670
1671 #[test]
1672 fn test_filter_not() {
1673 let filter = Filter::not(Filter::Equals("deleted".into(), FilterValue::Bool(true)));
1674
1675 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
1676 assert!(sql.contains("NOT"));
1677 }
1678
1679 #[test]
1680 fn test_filter_is_null() {
1681 let filter = Filter::IsNull("deleted_at".into());
1682 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1683 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1684 assert!(params.is_empty());
1685 }
1686
1687 #[test]
1688 fn test_filter_in() {
1689 let filter = Filter::In("status".into(), vec!["active".into(), "pending".into()]);
1690 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1691 assert!(sql.contains("IN"));
1692 assert_eq!(params.len(), 2);
1693 }
1694
1695 #[test]
1696 fn test_filter_contains() {
1697 let filter = Filter::Contains("email".into(), "example".into());
1698 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1699 assert!(sql.contains("LIKE"));
1700 assert_eq!(params.len(), 1);
1701 if let FilterValue::String(s) = ¶ms[0] {
1702 assert!(s.contains("%example%"));
1703 }
1704 }
1705
1706 #[test]
1709 fn test_filter_value_is_null() {
1710 assert!(FilterValue::Null.is_null());
1711 assert!(!FilterValue::Bool(false).is_null());
1712 assert!(!FilterValue::Int(0).is_null());
1713 assert!(!FilterValue::Float(0.0).is_null());
1714 assert!(!FilterValue::String("".to_string()).is_null());
1715 }
1716
1717 #[test]
1718 fn test_filter_value_from_i64() {
1719 assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
1720 assert_eq!(FilterValue::from(-100i64), FilterValue::Int(-100));
1721 }
1722
1723 #[test]
1724 #[allow(clippy::approx_constant)]
1725 fn test_filter_value_from_f64() {
1726 assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1727 }
1728
1729 #[test]
1730 fn test_filter_value_from_string() {
1731 assert_eq!(
1732 FilterValue::from("hello".to_string()),
1733 FilterValue::String("hello".to_string())
1734 );
1735 }
1736
1737 #[test]
1738 fn test_filter_value_from_vec() {
1739 let values: Vec<i32> = vec![1, 2, 3];
1740 let filter_val: FilterValue = values.into();
1741 if let FilterValue::List(list) = filter_val {
1742 assert_eq!(list.len(), 3);
1743 assert_eq!(list[0], FilterValue::Int(1));
1744 assert_eq!(list[1], FilterValue::Int(2));
1745 assert_eq!(list[2], FilterValue::Int(3));
1746 } else {
1747 panic!("Expected List");
1748 }
1749 }
1750
1751 #[test]
1752 fn test_filter_value_from_option_some() {
1753 let val: FilterValue = Some(42i32).into();
1754 assert_eq!(val, FilterValue::Int(42));
1755 }
1756
1757 #[test]
1758 fn test_filter_value_from_option_none() {
1759 let val: FilterValue = Option::<i32>::None.into();
1760 assert_eq!(val, FilterValue::Null);
1761 }
1762
1763 #[test]
1766 fn test_scalar_filter_not() {
1767 let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1768 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1769 assert_eq!(sql, r#""name" != $1"#);
1770 assert_eq!(params.len(), 1);
1771 }
1772
1773 #[test]
1774 fn test_scalar_filter_in() {
1775 let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1776 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1777 assert!(sql.contains("IN"));
1778 assert_eq!(params.len(), 2);
1779 }
1780
1781 #[test]
1782 fn test_scalar_filter_not_in() {
1783 let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1784 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1785 assert!(sql.contains("NOT IN"));
1786 assert_eq!(params.len(), 1);
1787 }
1788
1789 #[test]
1790 fn test_scalar_filter_lt() {
1791 let filter = ScalarFilter::Lt(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_lte() {
1799 let filter = ScalarFilter::Lte(100i32).into_filter("price");
1800 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1801 assert_eq!(sql, r#""price" <= $1"#);
1802 assert_eq!(params.len(), 1);
1803 }
1804
1805 #[test]
1806 fn test_scalar_filter_gt() {
1807 let filter = ScalarFilter::Gt(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_gte() {
1815 let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1816 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1817 assert_eq!(sql, r#""quantity" >= $1"#);
1818 assert_eq!(params.len(), 1);
1819 }
1820
1821 #[test]
1822 fn test_scalar_filter_starts_with() {
1823 let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1824 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1825 assert!(sql.contains("LIKE"));
1826 assert_eq!(params.len(), 1);
1827 if let FilterValue::String(s) = ¶ms[0] {
1828 assert!(s.starts_with("prefix"));
1829 assert!(s.ends_with("%"));
1830 }
1831 }
1832
1833 #[test]
1834 fn test_scalar_filter_ends_with() {
1835 let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
1836 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1837 assert!(sql.contains("LIKE"));
1838 assert_eq!(params.len(), 1);
1839 if let FilterValue::String(s) = ¶ms[0] {
1840 assert!(s.starts_with("%"));
1841 assert!(s.ends_with("suffix"));
1842 }
1843 }
1844
1845 #[test]
1846 fn test_scalar_filter_is_null() {
1847 let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
1848 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1849 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1850 assert!(params.is_empty());
1851 }
1852
1853 #[test]
1854 fn test_scalar_filter_is_not_null() {
1855 let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
1856 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1857 assert_eq!(sql, r#""name" IS NOT NULL"#);
1858 assert!(params.is_empty());
1859 }
1860
1861 #[test]
1864 fn test_filter_none() {
1865 let filter = Filter::none();
1866 assert!(filter.is_none());
1867 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1868 assert_eq!(sql, "TRUE"); assert!(params.is_empty());
1870 }
1871
1872 #[test]
1873 fn test_filter_not_equals() {
1874 let filter = Filter::NotEquals("status".into(), "deleted".into());
1875 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1876 assert_eq!(sql, r#""status" != $1"#);
1877 assert_eq!(params.len(), 1);
1878 }
1879
1880 #[test]
1881 fn test_filter_lte() {
1882 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
1883 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1884 assert_eq!(sql, r#""price" <= $1"#);
1885 assert_eq!(params.len(), 1);
1886 }
1887
1888 #[test]
1889 fn test_filter_gte() {
1890 let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
1891 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1892 assert_eq!(sql, r#""quantity" >= $1"#);
1893 assert_eq!(params.len(), 1);
1894 }
1895
1896 #[test]
1897 fn test_filter_not_in() {
1898 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
1899 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1900 assert!(sql.contains("NOT IN"));
1901 assert_eq!(params.len(), 2);
1902 }
1903
1904 #[test]
1905 fn test_filter_starts_with() {
1906 let filter = Filter::StartsWith("email".into(), "admin".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_ends_with() {
1914 let filter = Filter::EndsWith("email".into(), "@example.com".into());
1915 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1916 assert!(sql.contains("LIKE"));
1917 assert_eq!(params.len(), 1);
1918 }
1919
1920 #[test]
1921 fn test_filter_is_not_null() {
1922 let filter = Filter::IsNotNull("name".into());
1923 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1924 assert_eq!(sql, r#""name" IS NOT NULL"#);
1925 assert!(params.is_empty());
1926 }
1927
1928 #[test]
1931 fn test_filter_and_empty() {
1932 let filter = Filter::and([]);
1933 assert!(filter.is_none());
1934 }
1935
1936 #[test]
1937 fn test_filter_and_single() {
1938 let f = Filter::Equals("name".into(), "Alice".into());
1939 let combined = Filter::and([f.clone()]);
1940 assert_eq!(combined, f);
1941 }
1942
1943 #[test]
1944 fn test_filter_and_with_none() {
1945 let f1 = Filter::Equals("name".into(), "Alice".into());
1946 let f2 = Filter::None;
1947 let combined = Filter::and([f1.clone(), f2]);
1948 assert_eq!(combined, f1);
1949 }
1950
1951 #[test]
1952 fn test_filter_or_empty() {
1953 let filter = Filter::or([]);
1954 assert!(filter.is_none());
1955 }
1956
1957 #[test]
1958 fn test_filter_or_single() {
1959 let f = Filter::Equals("status".into(), "active".into());
1960 let combined = Filter::or([f.clone()]);
1961 assert_eq!(combined, f);
1962 }
1963
1964 #[test]
1965 fn test_filter_or_with_none() {
1966 let f1 = Filter::Equals("status".into(), "active".into());
1967 let f2 = Filter::None;
1968 let combined = Filter::or([f1.clone(), f2]);
1969 assert_eq!(combined, f1);
1970 }
1971
1972 #[test]
1973 fn test_filter_not_none() {
1974 let filter = Filter::not(Filter::None);
1975 assert!(filter.is_none());
1976 }
1977
1978 #[test]
1979 fn test_filter_and_then() {
1980 let f1 = Filter::Equals("name".into(), "Alice".into());
1981 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1982 let combined = f1.and_then(f2);
1983
1984 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
1985 assert!(sql.contains("AND"));
1986 assert_eq!(params.len(), 2);
1987 }
1988
1989 #[test]
1990 fn test_filter_and_then_with_none_first() {
1991 let f1 = Filter::None;
1992 let f2 = Filter::Equals("name".into(), "Bob".into());
1993 let combined = f1.and_then(f2.clone());
1994 assert_eq!(combined, f2);
1995 }
1996
1997 #[test]
1998 fn test_filter_and_then_with_none_second() {
1999 let f1 = Filter::Equals("name".into(), "Alice".into());
2000 let f2 = Filter::None;
2001 let combined = f1.clone().and_then(f2);
2002 assert_eq!(combined, f1);
2003 }
2004
2005 #[test]
2006 fn test_filter_and_then_chained() {
2007 let f1 = Filter::Equals("a".into(), "1".into());
2008 let f2 = Filter::Equals("b".into(), "2".into());
2009 let f3 = Filter::Equals("c".into(), "3".into());
2010 let combined = f1.and_then(f2).and_then(f3);
2011
2012 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2013 assert!(sql.contains("AND"));
2014 assert_eq!(params.len(), 3);
2015 }
2016
2017 #[test]
2018 fn test_filter_or_else() {
2019 let f1 = Filter::Equals("status".into(), "active".into());
2020 let f2 = Filter::Equals("status".into(), "pending".into());
2021 let combined = f1.or_else(f2);
2022
2023 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
2024 assert!(sql.contains("OR"));
2025 }
2026
2027 #[test]
2028 fn test_filter_or_else_with_none_first() {
2029 let f1 = Filter::None;
2030 let f2 = Filter::Equals("name".into(), "Bob".into());
2031 let combined = f1.or_else(f2.clone());
2032 assert_eq!(combined, f2);
2033 }
2034
2035 #[test]
2036 fn test_filter_or_else_with_none_second() {
2037 let f1 = Filter::Equals("name".into(), "Alice".into());
2038 let f2 = Filter::None;
2039 let combined = f1.clone().or_else(f2);
2040 assert_eq!(combined, f1);
2041 }
2042
2043 #[test]
2046 fn test_filter_nested_and_or() {
2047 let f1 = Filter::Equals("status".into(), "active".into());
2048 let f2 = Filter::and([
2049 Filter::Gt("age".into(), FilterValue::Int(18)),
2050 Filter::Lt("age".into(), FilterValue::Int(65)),
2051 ]);
2052 let combined = Filter::and([f1, f2]);
2053
2054 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2055 assert!(sql.contains("AND"));
2056 assert_eq!(params.len(), 3);
2057 }
2058
2059 #[test]
2060 fn test_filter_nested_not() {
2061 let inner = Filter::and([
2062 Filter::Equals("status".into(), "deleted".into()),
2063 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2064 ]);
2065 let filter = Filter::not(inner);
2066
2067 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2068 assert!(sql.contains("NOT"));
2069 assert!(sql.contains("AND"));
2070 assert_eq!(params.len(), 2);
2071 }
2072
2073 #[test]
2074 fn test_filter_with_json_value() {
2075 let json_val = serde_json::json!({"key": "value"});
2076 let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
2077 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2078 assert_eq!(sql, r#""metadata" = $1"#);
2079 assert_eq!(params.len(), 1);
2080 }
2081
2082 #[test]
2083 fn test_filter_in_empty_list() {
2084 let filter = Filter::In("status".into(), vec![]);
2085 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2086 assert!(
2088 sql.contains("FALSE")
2089 || sql.contains("1=0")
2090 || sql.is_empty()
2091 || sql.contains("status")
2092 );
2093 assert!(params.is_empty());
2094 }
2095
2096 #[test]
2097 fn test_filter_with_null_value() {
2098 let filter = Filter::IsNull("deleted_at".into());
2100 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2101 assert!(sql.contains("deleted_at"));
2102 assert!(sql.contains("IS NULL"));
2103 assert!(params.is_empty());
2104 }
2105
2106 #[test]
2109 fn test_and_builder_basic() {
2110 let filter = Filter::and_builder(3)
2111 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2112 .push(Filter::Gt("score".into(), FilterValue::Int(100)))
2113 .push(Filter::IsNotNull("email".into()))
2114 .build();
2115
2116 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2117 assert!(sql.contains("AND"));
2118 assert_eq!(params.len(), 2); }
2120
2121 #[test]
2122 fn test_and_builder_empty() {
2123 let filter = Filter::and_builder(0).build();
2124 assert!(filter.is_none());
2125 }
2126
2127 #[test]
2128 fn test_and_builder_single() {
2129 let filter = Filter::and_builder(1)
2130 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2131 .build();
2132
2133 assert!(matches!(filter, Filter::Equals(_, _)));
2135 }
2136
2137 #[test]
2138 fn test_and_builder_filters_none() {
2139 let filter = Filter::and_builder(3)
2140 .push(Filter::None)
2141 .push(Filter::Equals("id".into(), FilterValue::Int(1)))
2142 .push(Filter::None)
2143 .build();
2144
2145 assert!(matches!(filter, Filter::Equals(_, _)));
2147 }
2148
2149 #[test]
2150 fn test_and_builder_push_if() {
2151 let include_deleted = false;
2152 let filter = Filter::and_builder(2)
2153 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2154 .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
2155 .build();
2156
2157 assert!(matches!(filter, Filter::Equals(_, _)));
2159 }
2160
2161 #[test]
2162 fn test_or_builder_basic() {
2163 let filter = Filter::or_builder(2)
2164 .push(Filter::Equals(
2165 "role".into(),
2166 FilterValue::String("admin".into()),
2167 ))
2168 .push(Filter::Equals(
2169 "role".into(),
2170 FilterValue::String("moderator".into()),
2171 ))
2172 .build();
2173
2174 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2175 assert!(sql.contains("OR"));
2176 }
2177
2178 #[test]
2179 fn test_or_builder_empty() {
2180 let filter = Filter::or_builder(0).build();
2181 assert!(filter.is_none());
2182 }
2183
2184 #[test]
2185 fn test_or_builder_single() {
2186 let filter = Filter::or_builder(1)
2187 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2188 .build();
2189
2190 assert!(matches!(filter, Filter::Equals(_, _)));
2192 }
2193
2194 #[test]
2195 fn test_fluent_builder_and() {
2196 let filter = Filter::builder()
2197 .eq("status", "active")
2198 .gt("age", 18)
2199 .is_not_null("email")
2200 .build_and();
2201
2202 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2203 assert!(sql.contains("AND"));
2204 assert_eq!(params.len(), 2);
2205 }
2206
2207 #[test]
2208 fn test_fluent_builder_or() {
2209 let filter = Filter::builder()
2210 .eq("role", "admin")
2211 .eq("role", "moderator")
2212 .build_or();
2213
2214 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2215 assert!(sql.contains("OR"));
2216 }
2217
2218 #[test]
2219 fn test_fluent_builder_with_capacity() {
2220 let filter = Filter::builder()
2221 .with_capacity(5)
2222 .eq("a", 1)
2223 .ne("b", 2)
2224 .lt("c", 3)
2225 .lte("d", 4)
2226 .gte("e", 5)
2227 .build_and();
2228
2229 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2230 assert!(sql.contains("AND"));
2231 assert_eq!(params.len(), 5);
2232 }
2233
2234 #[test]
2235 fn test_fluent_builder_string_operations() {
2236 let filter = Filter::builder()
2237 .contains("name", "john")
2238 .starts_with("email", "admin")
2239 .ends_with("domain", ".com")
2240 .build_and();
2241
2242 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2243 assert!(sql.contains("LIKE"));
2244 }
2245
2246 #[test]
2247 fn test_fluent_builder_null_operations() {
2248 let filter = Filter::builder()
2249 .is_null("deleted_at")
2250 .is_not_null("created_at")
2251 .build_and();
2252
2253 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2254 assert!(sql.contains("IS NULL"));
2255 assert!(sql.contains("IS NOT NULL"));
2256 }
2257
2258 #[test]
2259 fn test_fluent_builder_in_operations() {
2260 let filter = Filter::builder()
2261 .is_in("status", vec!["pending", "processing"])
2262 .not_in("role", vec!["banned", "suspended"])
2263 .build_and();
2264
2265 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2266 assert!(sql.contains("IN"));
2267 assert!(sql.contains("NOT IN"));
2268 }
2269
2270 #[test]
2271 fn test_fluent_builder_filter_if() {
2272 let include_archived = false;
2273 let filter = Filter::builder()
2274 .eq("active", true)
2275 .filter_if(
2276 include_archived,
2277 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2278 )
2279 .build_and();
2280
2281 assert!(matches!(filter, Filter::Equals(_, _)));
2283 }
2284
2285 #[test]
2286 fn test_fluent_builder_filter_if_some() {
2287 let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2288 let filter = Filter::builder()
2289 .eq("id", 1)
2290 .filter_if_some(maybe_status)
2291 .build_and();
2292
2293 assert!(matches!(filter, Filter::And(_)));
2294 }
2295
2296 #[test]
2297 fn test_and_builder_extend() {
2298 let extra_filters = vec![
2299 Filter::Gt("score".into(), FilterValue::Int(100)),
2300 Filter::Lt("score".into(), FilterValue::Int(1000)),
2301 ];
2302
2303 let filter = Filter::and_builder(3)
2304 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2305 .extend(extra_filters)
2306 .build();
2307
2308 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2309 assert!(sql.contains("AND"));
2310 assert_eq!(params.len(), 3);
2311 }
2312
2313 #[test]
2314 fn test_builder_len_and_is_empty() {
2315 let mut builder = AndFilterBuilder::new();
2316 assert!(builder.is_empty());
2317 assert_eq!(builder.len(), 0);
2318
2319 builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2320 assert!(!builder.is_empty());
2321 assert_eq!(builder.len(), 1);
2322 }
2323
2324 #[test]
2327 fn test_and2_both_valid() {
2328 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2329 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2330 let filter = Filter::and2(a, b);
2331
2332 assert!(matches!(filter, Filter::And(_)));
2333 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2334 assert!(sql.contains("AND"));
2335 assert_eq!(params.len(), 2);
2336 }
2337
2338 #[test]
2339 fn test_and2_first_none() {
2340 let a = Filter::None;
2341 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2342 let filter = Filter::and2(a, b.clone());
2343
2344 assert_eq!(filter, b);
2345 }
2346
2347 #[test]
2348 fn test_and2_second_none() {
2349 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2350 let b = Filter::None;
2351 let filter = Filter::and2(a.clone(), b);
2352
2353 assert_eq!(filter, a);
2354 }
2355
2356 #[test]
2357 fn test_and2_both_none() {
2358 let filter = Filter::and2(Filter::None, Filter::None);
2359 assert!(filter.is_none());
2360 }
2361
2362 #[test]
2363 fn test_or2_both_valid() {
2364 let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2365 let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2366 let filter = Filter::or2(a, b);
2367
2368 assert!(matches!(filter, Filter::Or(_)));
2369 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2370 assert!(sql.contains("OR"));
2371 }
2372
2373 #[test]
2374 fn test_or2_first_none() {
2375 let a = Filter::None;
2376 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2377 let filter = Filter::or2(a, b.clone());
2378
2379 assert_eq!(filter, b);
2380 }
2381
2382 #[test]
2383 fn test_or2_second_none() {
2384 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2385 let b = Filter::None;
2386 let filter = Filter::or2(a.clone(), b);
2387
2388 assert_eq!(filter, a);
2389 }
2390
2391 #[test]
2392 fn test_or2_both_none() {
2393 let filter = Filter::or2(Filter::None, Filter::None);
2394 assert!(filter.is_none());
2395 }
2396
2397 #[test]
2400 fn to_sql_quotes_column_names_against_injection() {
2401 use crate::dialect::{Mssql, Mysql, Postgres};
2402
2403 let filter = Filter::Equals(r#"id" OR 1=1--"#.into(), FilterValue::Int(1));
2405
2406 let (sql_pg, _) = filter.to_sql(0, &Postgres);
2407 assert!(
2408 sql_pg.starts_with(r#""id"" OR 1=1--" ="#),
2409 "postgres did not quote col; got: {sql_pg}"
2410 );
2411
2412 let (sql_my, _) = filter.to_sql(0, &Mysql);
2413 assert!(
2414 sql_my.starts_with(r#"`id" OR 1=1--` ="#),
2415 "mysql did not quote col; got: {sql_my}"
2416 );
2417
2418 let (sql_ms, _) = filter.to_sql(0, &Mssql);
2419 assert!(
2420 sql_ms.starts_with(r#"[id" OR 1=1--] ="#),
2421 "mssql did not quote col; got: {sql_ms}"
2422 );
2423 }
2424
2425 #[test]
2426 fn to_sql_quotes_in_list_column_names() {
2427 use crate::dialect::Postgres;
2428 let filter = Filter::In("id".into(), vec![FilterValue::Int(1), FilterValue::Int(2)]);
2429 let (sql, _) = filter.to_sql(0, &Postgres);
2430 assert!(
2431 sql.starts_with(r#""id" IN ("#),
2432 "expected quoted id on IN, got: {sql}"
2433 );
2434 }
2435
2436 #[test]
2437 fn to_sql_quotes_null_checks() {
2438 use crate::dialect::Postgres;
2439 let filter = Filter::IsNull("deleted_at".into());
2440 let (sql, _) = filter.to_sql(0, &Postgres);
2441 assert_eq!(sql, r#""deleted_at" IS NULL"#);
2442 }
2443
2444 #[test]
2445 fn to_sql_quotes_comparison_operators() {
2446 use crate::dialect::Postgres;
2447
2448 let filter = Filter::Lt("age".into(), FilterValue::Int(18));
2449 let (sql, _) = filter.to_sql(0, &Postgres);
2450 assert!(sql.starts_with(r#""age" < "#), "Lt not quoted: {sql}");
2451
2452 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
2453 let (sql, _) = filter.to_sql(0, &Postgres);
2454 assert!(sql.starts_with(r#""price" <= "#), "Lte not quoted: {sql}");
2455
2456 let filter = Filter::Gt("score".into(), FilterValue::Int(0));
2457 let (sql, _) = filter.to_sql(0, &Postgres);
2458 assert!(sql.starts_with(r#""score" > "#), "Gt not quoted: {sql}");
2459
2460 let filter = Filter::Gte("quantity".into(), FilterValue::Int(1));
2461 let (sql, _) = filter.to_sql(0, &Postgres);
2462 assert!(
2463 sql.starts_with(r#""quantity" >= "#),
2464 "Gte not quoted: {sql}"
2465 );
2466
2467 let filter = Filter::NotEquals("status".into(), "deleted".into());
2468 let (sql, _) = filter.to_sql(0, &Postgres);
2469 assert!(
2470 sql.starts_with(r#""status" != "#),
2471 "NotEquals not quoted: {sql}"
2472 );
2473 }
2474
2475 #[test]
2476 fn to_sql_quotes_like_operators() {
2477 use crate::dialect::Postgres;
2478
2479 let filter = Filter::Contains("email".into(), "example".into());
2480 let (sql, _) = filter.to_sql(0, &Postgres);
2481 assert!(
2482 sql.starts_with(r#""email" LIKE "#),
2483 "Contains not quoted: {sql}"
2484 );
2485
2486 let filter = Filter::StartsWith("name".into(), "admin".into());
2487 let (sql, _) = filter.to_sql(0, &Postgres);
2488 assert!(
2489 sql.starts_with(r#""name" LIKE "#),
2490 "StartsWith not quoted: {sql}"
2491 );
2492
2493 let filter = Filter::EndsWith("domain".into(), ".com".into());
2494 let (sql, _) = filter.to_sql(0, &Postgres);
2495 assert!(
2496 sql.starts_with(r#""domain" LIKE "#),
2497 "EndsWith not quoted: {sql}"
2498 );
2499 }
2500
2501 #[test]
2502 fn to_sql_quotes_not_in() {
2503 use crate::dialect::Postgres;
2504 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
2505 let (sql, _) = filter.to_sql(0, &Postgres);
2506 assert!(
2507 sql.starts_with(r#""status" NOT IN ("#),
2508 "NotIn not quoted: {sql}"
2509 );
2510 }
2511
2512 #[test]
2513 fn to_sql_quotes_is_not_null() {
2514 use crate::dialect::Postgres;
2515 let filter = Filter::IsNotNull("verified_at".into());
2516 let (sql, _) = filter.to_sql(0, &Postgres);
2517 assert_eq!(sql, r#""verified_at" IS NOT NULL"#);
2518 }
2519
2520 #[test]
2521 fn filter_value_from_u64_in_range() {
2522 assert_eq!(FilterValue::from(42u64), FilterValue::Int(42));
2523 assert_eq!(FilterValue::from(0u64), FilterValue::Int(0));
2524 let max_safe = i64::MAX as u64;
2525 assert_eq!(FilterValue::from(max_safe), FilterValue::Int(i64::MAX));
2526 }
2527
2528 #[test]
2529 #[should_panic(expected = "u64 value exceeds i64::MAX")]
2530 fn filter_value_from_u64_overflow_panics() {
2531 let _ = FilterValue::from(u64::MAX);
2532 }
2533
2534 #[test]
2535 fn filter_value_from_chrono_datetime_utc_rfc3339() {
2536 use chrono::{TimeZone, Utc};
2537 let dt = Utc.with_ymd_and_hms(2020, 1, 15, 10, 30, 45).unwrap();
2538 let fv = FilterValue::from(dt);
2539 assert_eq!(
2540 fv,
2541 FilterValue::String("2020-01-15T10:30:45.000000Z".to_string())
2542 );
2543 }
2544
2545 #[test]
2546 fn filter_value_from_chrono_naive_datetime_iso() {
2547 use chrono::NaiveDate;
2548 let dt = NaiveDate::from_ymd_opt(2020, 1, 15)
2549 .unwrap()
2550 .and_hms_opt(10, 30, 45)
2551 .unwrap();
2552 let fv = FilterValue::from(dt);
2553 assert_eq!(
2554 fv,
2555 FilterValue::String("2020-01-15T10:30:45.000000".to_string())
2556 );
2557 }
2558
2559 #[test]
2560 fn filter_value_from_chrono_naive_date() {
2561 use chrono::NaiveDate;
2562 let d = NaiveDate::from_ymd_opt(2020, 1, 15).unwrap();
2563 assert_eq!(
2564 FilterValue::from(d),
2565 FilterValue::String("2020-01-15".to_string())
2566 );
2567 }
2568
2569 #[test]
2570 fn filter_value_from_chrono_naive_time() {
2571 use chrono::NaiveTime;
2572 let t = NaiveTime::from_hms_opt(10, 30, 45).unwrap();
2573 assert_eq!(
2574 FilterValue::from(t),
2575 FilterValue::String("10:30:45.000000".to_string())
2576 );
2577 }
2578
2579 #[test]
2586 fn filter_value_from_uuid_is_lowercase_hyphenated() {
2587 use uuid::Uuid;
2592 let u = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
2593 match FilterValue::from(u) {
2594 FilterValue::String(ref s) => {
2595 assert_eq!(s, "550e8400-e29b-41d4-a716-446655440000");
2596 assert_eq!(s, &u.to_string());
2597 }
2598 other => panic!("expected FilterValue::String, got {other:?}"),
2599 }
2600 }
2601
2602 #[test]
2603 fn filter_value_from_uuid_nil_round_trips() {
2604 use uuid::Uuid;
2605 let u = Uuid::nil();
2606 assert_eq!(
2607 FilterValue::from(u),
2608 FilterValue::String("00000000-0000-0000-0000-000000000000".to_string())
2609 );
2610 }
2611
2612 #[test]
2613 fn filter_value_from_decimal_uses_to_string_not_f64() {
2614 use rust_decimal::Decimal;
2618 use std::str::FromStr;
2619 let d = Decimal::from_str("3.14").unwrap();
2620 assert_eq!(
2621 FilterValue::from(d),
2622 FilterValue::String("3.14".to_string())
2623 );
2624 }
2625
2626 #[test]
2627 fn filter_value_from_decimal_high_precision_preserved() {
2628 use rust_decimal::Decimal;
2629 use std::str::FromStr;
2630 let d = Decimal::from_str("1234567890.1234567890").unwrap();
2632 match FilterValue::from(d) {
2633 FilterValue::String(ref s) => {
2634 assert_eq!(s, "1234567890.1234567890");
2635 }
2636 other => panic!("expected FilterValue::String, got {other:?}"),
2637 }
2638 }
2639
2640 #[test]
2641 fn filter_value_from_serde_json_value_keeps_json_variant() {
2642 let v = serde_json::json!({"key": "value", "nested": [1, 2, 3]});
2643 match FilterValue::from(v.clone()) {
2644 FilterValue::Json(inner) => {
2645 assert_eq!(inner, v);
2646 }
2647 other => panic!("expected FilterValue::Json, got {other:?}"),
2648 }
2649 }
2650
2651 #[test]
2652 fn filter_value_from_serde_json_null_keeps_json_variant() {
2653 let v = serde_json::Value::Null;
2658 match FilterValue::from(v) {
2659 FilterValue::Json(serde_json::Value::Null) => {}
2660 other => panic!("expected FilterValue::Json(Null), got {other:?}"),
2661 }
2662 }
2663
2664 #[test]
2665 fn filter_value_from_option_none_maps_to_null() {
2666 let none_i32: Option<i32> = None;
2669 assert_eq!(FilterValue::from(none_i32), FilterValue::Null);
2670 let none_string: Option<String> = None;
2671 assert_eq!(FilterValue::from(none_string), FilterValue::Null);
2672 }
2673
2674 #[test]
2675 fn filter_value_from_signed_integer_extremes() {
2676 assert_eq!(FilterValue::from(i8::MIN), FilterValue::Int(i8::MIN as i64));
2679 assert_eq!(FilterValue::from(i8::MAX), FilterValue::Int(i8::MAX as i64));
2680 assert_eq!(
2681 FilterValue::from(i16::MIN),
2682 FilterValue::Int(i16::MIN as i64)
2683 );
2684 assert_eq!(
2685 FilterValue::from(i16::MAX),
2686 FilterValue::Int(i16::MAX as i64)
2687 );
2688 }
2689
2690 #[test]
2691 fn filter_value_from_unsigned_integer_extremes() {
2692 assert_eq!(FilterValue::from(u8::MAX), FilterValue::Int(u8::MAX as i64));
2695 assert_eq!(
2696 FilterValue::from(u16::MAX),
2697 FilterValue::Int(u16::MAX as i64)
2698 );
2699 assert_eq!(
2700 FilterValue::from(u32::MAX),
2701 FilterValue::Int(u32::MAX as i64)
2702 );
2703 assert_eq!(FilterValue::from(u32::MAX), FilterValue::Int(4_294_967_295));
2705 }
2706
2707 #[test]
2708 fn filter_value_from_f32_widens_to_f64() {
2709 let v: f32 = 1.5;
2714 assert_eq!(FilterValue::from(v), FilterValue::Float(1.5));
2715 }
2716
2717 #[test]
2724 fn to_filter_value_option_some_some() {
2725 let v: Option<i32> = Some(42);
2726 assert_eq!(v.to_filter_value(), FilterValue::Int(42));
2727 }
2728
2729 #[test]
2730 fn to_filter_value_option_none_is_null() {
2731 let v: Option<i32> = None;
2732 assert_eq!(v.to_filter_value(), FilterValue::Null);
2733 }
2734
2735 #[test]
2736 fn to_filter_value_uuid_is_string() {
2737 let id = uuid::Uuid::nil();
2738 assert_eq!(id.to_filter_value(), FilterValue::String(id.to_string()));
2739 }
2740
2741 #[test]
2742 fn to_filter_value_bool_is_bool() {
2743 assert_eq!(true.to_filter_value(), FilterValue::Bool(true));
2744 }
2745}