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 fn test_filter_value_from_f64() {
1717 assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1718 }
1719
1720 #[test]
1721 fn test_filter_value_from_string() {
1722 assert_eq!(
1723 FilterValue::from("hello".to_string()),
1724 FilterValue::String("hello".to_string())
1725 );
1726 }
1727
1728 #[test]
1729 fn test_filter_value_from_vec() {
1730 let values: Vec<i32> = vec![1, 2, 3];
1731 let filter_val: FilterValue = values.into();
1732 if let FilterValue::List(list) = filter_val {
1733 assert_eq!(list.len(), 3);
1734 assert_eq!(list[0], FilterValue::Int(1));
1735 assert_eq!(list[1], FilterValue::Int(2));
1736 assert_eq!(list[2], FilterValue::Int(3));
1737 } else {
1738 panic!("Expected List");
1739 }
1740 }
1741
1742 #[test]
1743 fn test_filter_value_from_option_some() {
1744 let val: FilterValue = Some(42i32).into();
1745 assert_eq!(val, FilterValue::Int(42));
1746 }
1747
1748 #[test]
1749 fn test_filter_value_from_option_none() {
1750 let val: FilterValue = Option::<i32>::None.into();
1751 assert_eq!(val, FilterValue::Null);
1752 }
1753
1754 #[test]
1757 fn test_scalar_filter_not() {
1758 let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1759 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1760 assert_eq!(sql, r#""name" != $1"#);
1761 assert_eq!(params.len(), 1);
1762 }
1763
1764 #[test]
1765 fn test_scalar_filter_in() {
1766 let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1767 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1768 assert!(sql.contains("IN"));
1769 assert_eq!(params.len(), 2);
1770 }
1771
1772 #[test]
1773 fn test_scalar_filter_not_in() {
1774 let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1775 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1776 assert!(sql.contains("NOT IN"));
1777 assert_eq!(params.len(), 1);
1778 }
1779
1780 #[test]
1781 fn test_scalar_filter_lt() {
1782 let filter = ScalarFilter::Lt(100i32).into_filter("price");
1783 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1784 assert_eq!(sql, r#""price" < $1"#);
1785 assert_eq!(params.len(), 1);
1786 }
1787
1788 #[test]
1789 fn test_scalar_filter_lte() {
1790 let filter = ScalarFilter::Lte(100i32).into_filter("price");
1791 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1792 assert_eq!(sql, r#""price" <= $1"#);
1793 assert_eq!(params.len(), 1);
1794 }
1795
1796 #[test]
1797 fn test_scalar_filter_gt() {
1798 let filter = ScalarFilter::Gt(0i32).into_filter("quantity");
1799 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1800 assert_eq!(sql, r#""quantity" > $1"#);
1801 assert_eq!(params.len(), 1);
1802 }
1803
1804 #[test]
1805 fn test_scalar_filter_gte() {
1806 let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1807 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1808 assert_eq!(sql, r#""quantity" >= $1"#);
1809 assert_eq!(params.len(), 1);
1810 }
1811
1812 #[test]
1813 fn test_scalar_filter_starts_with() {
1814 let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1815 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1816 assert!(sql.contains("LIKE"));
1817 assert_eq!(params.len(), 1);
1818 if let FilterValue::String(s) = ¶ms[0] {
1819 assert!(s.starts_with("prefix"));
1820 assert!(s.ends_with("%"));
1821 }
1822 }
1823
1824 #[test]
1825 fn test_scalar_filter_ends_with() {
1826 let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
1827 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1828 assert!(sql.contains("LIKE"));
1829 assert_eq!(params.len(), 1);
1830 if let FilterValue::String(s) = ¶ms[0] {
1831 assert!(s.starts_with("%"));
1832 assert!(s.ends_with("suffix"));
1833 }
1834 }
1835
1836 #[test]
1837 fn test_scalar_filter_is_null() {
1838 let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
1839 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1840 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1841 assert!(params.is_empty());
1842 }
1843
1844 #[test]
1845 fn test_scalar_filter_is_not_null() {
1846 let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
1847 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1848 assert_eq!(sql, r#""name" IS NOT NULL"#);
1849 assert!(params.is_empty());
1850 }
1851
1852 #[test]
1855 fn test_filter_none() {
1856 let filter = Filter::none();
1857 assert!(filter.is_none());
1858 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1859 assert_eq!(sql, "TRUE"); assert!(params.is_empty());
1861 }
1862
1863 #[test]
1864 fn test_filter_not_equals() {
1865 let filter = Filter::NotEquals("status".into(), "deleted".into());
1866 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1867 assert_eq!(sql, r#""status" != $1"#);
1868 assert_eq!(params.len(), 1);
1869 }
1870
1871 #[test]
1872 fn test_filter_lte() {
1873 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
1874 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1875 assert_eq!(sql, r#""price" <= $1"#);
1876 assert_eq!(params.len(), 1);
1877 }
1878
1879 #[test]
1880 fn test_filter_gte() {
1881 let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
1882 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1883 assert_eq!(sql, r#""quantity" >= $1"#);
1884 assert_eq!(params.len(), 1);
1885 }
1886
1887 #[test]
1888 fn test_filter_not_in() {
1889 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
1890 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1891 assert!(sql.contains("NOT IN"));
1892 assert_eq!(params.len(), 2);
1893 }
1894
1895 #[test]
1896 fn test_filter_starts_with() {
1897 let filter = Filter::StartsWith("email".into(), "admin".into());
1898 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1899 assert!(sql.contains("LIKE"));
1900 assert_eq!(params.len(), 1);
1901 }
1902
1903 #[test]
1904 fn test_filter_ends_with() {
1905 let filter = Filter::EndsWith("email".into(), "@example.com".into());
1906 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1907 assert!(sql.contains("LIKE"));
1908 assert_eq!(params.len(), 1);
1909 }
1910
1911 #[test]
1912 fn test_filter_is_not_null() {
1913 let filter = Filter::IsNotNull("name".into());
1914 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1915 assert_eq!(sql, r#""name" IS NOT NULL"#);
1916 assert!(params.is_empty());
1917 }
1918
1919 #[test]
1922 fn test_filter_and_empty() {
1923 let filter = Filter::and([]);
1924 assert!(filter.is_none());
1925 }
1926
1927 #[test]
1928 fn test_filter_and_single() {
1929 let f = Filter::Equals("name".into(), "Alice".into());
1930 let combined = Filter::and([f.clone()]);
1931 assert_eq!(combined, f);
1932 }
1933
1934 #[test]
1935 fn test_filter_and_with_none() {
1936 let f1 = Filter::Equals("name".into(), "Alice".into());
1937 let f2 = Filter::None;
1938 let combined = Filter::and([f1.clone(), f2]);
1939 assert_eq!(combined, f1);
1940 }
1941
1942 #[test]
1943 fn test_filter_or_empty() {
1944 let filter = Filter::or([]);
1945 assert!(filter.is_none());
1946 }
1947
1948 #[test]
1949 fn test_filter_or_single() {
1950 let f = Filter::Equals("status".into(), "active".into());
1951 let combined = Filter::or([f.clone()]);
1952 assert_eq!(combined, f);
1953 }
1954
1955 #[test]
1956 fn test_filter_or_with_none() {
1957 let f1 = Filter::Equals("status".into(), "active".into());
1958 let f2 = Filter::None;
1959 let combined = Filter::or([f1.clone(), f2]);
1960 assert_eq!(combined, f1);
1961 }
1962
1963 #[test]
1964 fn test_filter_not_none() {
1965 let filter = Filter::not(Filter::None);
1966 assert!(filter.is_none());
1967 }
1968
1969 #[test]
1970 fn test_filter_and_then() {
1971 let f1 = Filter::Equals("name".into(), "Alice".into());
1972 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1973 let combined = f1.and_then(f2);
1974
1975 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
1976 assert!(sql.contains("AND"));
1977 assert_eq!(params.len(), 2);
1978 }
1979
1980 #[test]
1981 fn test_filter_and_then_with_none_first() {
1982 let f1 = Filter::None;
1983 let f2 = Filter::Equals("name".into(), "Bob".into());
1984 let combined = f1.and_then(f2.clone());
1985 assert_eq!(combined, f2);
1986 }
1987
1988 #[test]
1989 fn test_filter_and_then_with_none_second() {
1990 let f1 = Filter::Equals("name".into(), "Alice".into());
1991 let f2 = Filter::None;
1992 let combined = f1.clone().and_then(f2);
1993 assert_eq!(combined, f1);
1994 }
1995
1996 #[test]
1997 fn test_filter_and_then_chained() {
1998 let f1 = Filter::Equals("a".into(), "1".into());
1999 let f2 = Filter::Equals("b".into(), "2".into());
2000 let f3 = Filter::Equals("c".into(), "3".into());
2001 let combined = f1.and_then(f2).and_then(f3);
2002
2003 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2004 assert!(sql.contains("AND"));
2005 assert_eq!(params.len(), 3);
2006 }
2007
2008 #[test]
2009 fn test_filter_or_else() {
2010 let f1 = Filter::Equals("status".into(), "active".into());
2011 let f2 = Filter::Equals("status".into(), "pending".into());
2012 let combined = f1.or_else(f2);
2013
2014 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
2015 assert!(sql.contains("OR"));
2016 }
2017
2018 #[test]
2019 fn test_filter_or_else_with_none_first() {
2020 let f1 = Filter::None;
2021 let f2 = Filter::Equals("name".into(), "Bob".into());
2022 let combined = f1.or_else(f2.clone());
2023 assert_eq!(combined, f2);
2024 }
2025
2026 #[test]
2027 fn test_filter_or_else_with_none_second() {
2028 let f1 = Filter::Equals("name".into(), "Alice".into());
2029 let f2 = Filter::None;
2030 let combined = f1.clone().or_else(f2);
2031 assert_eq!(combined, f1);
2032 }
2033
2034 #[test]
2037 fn test_filter_nested_and_or() {
2038 let f1 = Filter::Equals("status".into(), "active".into());
2039 let f2 = Filter::and([
2040 Filter::Gt("age".into(), FilterValue::Int(18)),
2041 Filter::Lt("age".into(), FilterValue::Int(65)),
2042 ]);
2043 let combined = Filter::and([f1, f2]);
2044
2045 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2046 assert!(sql.contains("AND"));
2047 assert_eq!(params.len(), 3);
2048 }
2049
2050 #[test]
2051 fn test_filter_nested_not() {
2052 let inner = Filter::and([
2053 Filter::Equals("status".into(), "deleted".into()),
2054 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2055 ]);
2056 let filter = Filter::not(inner);
2057
2058 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2059 assert!(sql.contains("NOT"));
2060 assert!(sql.contains("AND"));
2061 assert_eq!(params.len(), 2);
2062 }
2063
2064 #[test]
2065 fn test_filter_with_json_value() {
2066 let json_val = serde_json::json!({"key": "value"});
2067 let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
2068 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2069 assert_eq!(sql, r#""metadata" = $1"#);
2070 assert_eq!(params.len(), 1);
2071 }
2072
2073 #[test]
2074 fn test_filter_in_empty_list() {
2075 let filter = Filter::In("status".into(), vec![]);
2076 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2077 assert!(
2079 sql.contains("FALSE")
2080 || sql.contains("1=0")
2081 || sql.is_empty()
2082 || sql.contains("status")
2083 );
2084 assert!(params.is_empty());
2085 }
2086
2087 #[test]
2088 fn test_filter_with_null_value() {
2089 let filter = Filter::IsNull("deleted_at".into());
2091 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2092 assert!(sql.contains("deleted_at"));
2093 assert!(sql.contains("IS NULL"));
2094 assert!(params.is_empty());
2095 }
2096
2097 #[test]
2100 fn test_and_builder_basic() {
2101 let filter = Filter::and_builder(3)
2102 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2103 .push(Filter::Gt("score".into(), FilterValue::Int(100)))
2104 .push(Filter::IsNotNull("email".into()))
2105 .build();
2106
2107 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2108 assert!(sql.contains("AND"));
2109 assert_eq!(params.len(), 2); }
2111
2112 #[test]
2113 fn test_and_builder_empty() {
2114 let filter = Filter::and_builder(0).build();
2115 assert!(filter.is_none());
2116 }
2117
2118 #[test]
2119 fn test_and_builder_single() {
2120 let filter = Filter::and_builder(1)
2121 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2122 .build();
2123
2124 assert!(matches!(filter, Filter::Equals(_, _)));
2126 }
2127
2128 #[test]
2129 fn test_and_builder_filters_none() {
2130 let filter = Filter::and_builder(3)
2131 .push(Filter::None)
2132 .push(Filter::Equals("id".into(), FilterValue::Int(1)))
2133 .push(Filter::None)
2134 .build();
2135
2136 assert!(matches!(filter, Filter::Equals(_, _)));
2138 }
2139
2140 #[test]
2141 fn test_and_builder_push_if() {
2142 let include_deleted = false;
2143 let filter = Filter::and_builder(2)
2144 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2145 .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
2146 .build();
2147
2148 assert!(matches!(filter, Filter::Equals(_, _)));
2150 }
2151
2152 #[test]
2153 fn test_or_builder_basic() {
2154 let filter = Filter::or_builder(2)
2155 .push(Filter::Equals(
2156 "role".into(),
2157 FilterValue::String("admin".into()),
2158 ))
2159 .push(Filter::Equals(
2160 "role".into(),
2161 FilterValue::String("moderator".into()),
2162 ))
2163 .build();
2164
2165 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2166 assert!(sql.contains("OR"));
2167 }
2168
2169 #[test]
2170 fn test_or_builder_empty() {
2171 let filter = Filter::or_builder(0).build();
2172 assert!(filter.is_none());
2173 }
2174
2175 #[test]
2176 fn test_or_builder_single() {
2177 let filter = Filter::or_builder(1)
2178 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2179 .build();
2180
2181 assert!(matches!(filter, Filter::Equals(_, _)));
2183 }
2184
2185 #[test]
2186 fn test_fluent_builder_and() {
2187 let filter = Filter::builder()
2188 .eq("status", "active")
2189 .gt("age", 18)
2190 .is_not_null("email")
2191 .build_and();
2192
2193 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2194 assert!(sql.contains("AND"));
2195 assert_eq!(params.len(), 2);
2196 }
2197
2198 #[test]
2199 fn test_fluent_builder_or() {
2200 let filter = Filter::builder()
2201 .eq("role", "admin")
2202 .eq("role", "moderator")
2203 .build_or();
2204
2205 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2206 assert!(sql.contains("OR"));
2207 }
2208
2209 #[test]
2210 fn test_fluent_builder_with_capacity() {
2211 let filter = Filter::builder()
2212 .with_capacity(5)
2213 .eq("a", 1)
2214 .ne("b", 2)
2215 .lt("c", 3)
2216 .lte("d", 4)
2217 .gte("e", 5)
2218 .build_and();
2219
2220 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2221 assert!(sql.contains("AND"));
2222 assert_eq!(params.len(), 5);
2223 }
2224
2225 #[test]
2226 fn test_fluent_builder_string_operations() {
2227 let filter = Filter::builder()
2228 .contains("name", "john")
2229 .starts_with("email", "admin")
2230 .ends_with("domain", ".com")
2231 .build_and();
2232
2233 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2234 assert!(sql.contains("LIKE"));
2235 }
2236
2237 #[test]
2238 fn test_fluent_builder_null_operations() {
2239 let filter = Filter::builder()
2240 .is_null("deleted_at")
2241 .is_not_null("created_at")
2242 .build_and();
2243
2244 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2245 assert!(sql.contains("IS NULL"));
2246 assert!(sql.contains("IS NOT NULL"));
2247 }
2248
2249 #[test]
2250 fn test_fluent_builder_in_operations() {
2251 let filter = Filter::builder()
2252 .is_in("status", vec!["pending", "processing"])
2253 .not_in("role", vec!["banned", "suspended"])
2254 .build_and();
2255
2256 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2257 assert!(sql.contains("IN"));
2258 assert!(sql.contains("NOT IN"));
2259 }
2260
2261 #[test]
2262 fn test_fluent_builder_filter_if() {
2263 let include_archived = false;
2264 let filter = Filter::builder()
2265 .eq("active", true)
2266 .filter_if(
2267 include_archived,
2268 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2269 )
2270 .build_and();
2271
2272 assert!(matches!(filter, Filter::Equals(_, _)));
2274 }
2275
2276 #[test]
2277 fn test_fluent_builder_filter_if_some() {
2278 let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2279 let filter = Filter::builder()
2280 .eq("id", 1)
2281 .filter_if_some(maybe_status)
2282 .build_and();
2283
2284 assert!(matches!(filter, Filter::And(_)));
2285 }
2286
2287 #[test]
2288 fn test_and_builder_extend() {
2289 let extra_filters = vec![
2290 Filter::Gt("score".into(), FilterValue::Int(100)),
2291 Filter::Lt("score".into(), FilterValue::Int(1000)),
2292 ];
2293
2294 let filter = Filter::and_builder(3)
2295 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2296 .extend(extra_filters)
2297 .build();
2298
2299 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2300 assert!(sql.contains("AND"));
2301 assert_eq!(params.len(), 3);
2302 }
2303
2304 #[test]
2305 fn test_builder_len_and_is_empty() {
2306 let mut builder = AndFilterBuilder::new();
2307 assert!(builder.is_empty());
2308 assert_eq!(builder.len(), 0);
2309
2310 builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2311 assert!(!builder.is_empty());
2312 assert_eq!(builder.len(), 1);
2313 }
2314
2315 #[test]
2318 fn test_and2_both_valid() {
2319 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2320 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2321 let filter = Filter::and2(a, b);
2322
2323 assert!(matches!(filter, Filter::And(_)));
2324 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2325 assert!(sql.contains("AND"));
2326 assert_eq!(params.len(), 2);
2327 }
2328
2329 #[test]
2330 fn test_and2_first_none() {
2331 let a = Filter::None;
2332 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2333 let filter = Filter::and2(a, b.clone());
2334
2335 assert_eq!(filter, b);
2336 }
2337
2338 #[test]
2339 fn test_and2_second_none() {
2340 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2341 let b = Filter::None;
2342 let filter = Filter::and2(a.clone(), b);
2343
2344 assert_eq!(filter, a);
2345 }
2346
2347 #[test]
2348 fn test_and2_both_none() {
2349 let filter = Filter::and2(Filter::None, Filter::None);
2350 assert!(filter.is_none());
2351 }
2352
2353 #[test]
2354 fn test_or2_both_valid() {
2355 let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2356 let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2357 let filter = Filter::or2(a, b);
2358
2359 assert!(matches!(filter, Filter::Or(_)));
2360 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2361 assert!(sql.contains("OR"));
2362 }
2363
2364 #[test]
2365 fn test_or2_first_none() {
2366 let a = Filter::None;
2367 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2368 let filter = Filter::or2(a, b.clone());
2369
2370 assert_eq!(filter, b);
2371 }
2372
2373 #[test]
2374 fn test_or2_second_none() {
2375 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2376 let b = Filter::None;
2377 let filter = Filter::or2(a.clone(), b);
2378
2379 assert_eq!(filter, a);
2380 }
2381
2382 #[test]
2383 fn test_or2_both_none() {
2384 let filter = Filter::or2(Filter::None, Filter::None);
2385 assert!(filter.is_none());
2386 }
2387
2388 #[test]
2391 fn to_sql_quotes_column_names_against_injection() {
2392 use crate::dialect::{Mssql, Mysql, Postgres};
2393
2394 let filter = Filter::Equals(r#"id" OR 1=1--"#.into(), FilterValue::Int(1));
2396
2397 let (sql_pg, _) = filter.to_sql(0, &Postgres);
2398 assert!(
2399 sql_pg.starts_with(r#""id"" OR 1=1--" ="#),
2400 "postgres did not quote col; got: {sql_pg}"
2401 );
2402
2403 let (sql_my, _) = filter.to_sql(0, &Mysql);
2404 assert!(
2405 sql_my.starts_with(r#"`id" OR 1=1--` ="#),
2406 "mysql did not quote col; got: {sql_my}"
2407 );
2408
2409 let (sql_ms, _) = filter.to_sql(0, &Mssql);
2410 assert!(
2411 sql_ms.starts_with(r#"[id" OR 1=1--] ="#),
2412 "mssql did not quote col; got: {sql_ms}"
2413 );
2414 }
2415
2416 #[test]
2417 fn to_sql_quotes_in_list_column_names() {
2418 use crate::dialect::Postgres;
2419 let filter = Filter::In("id".into(), vec![FilterValue::Int(1), FilterValue::Int(2)]);
2420 let (sql, _) = filter.to_sql(0, &Postgres);
2421 assert!(
2422 sql.starts_with(r#""id" IN ("#),
2423 "expected quoted id on IN, got: {sql}"
2424 );
2425 }
2426
2427 #[test]
2428 fn to_sql_quotes_null_checks() {
2429 use crate::dialect::Postgres;
2430 let filter = Filter::IsNull("deleted_at".into());
2431 let (sql, _) = filter.to_sql(0, &Postgres);
2432 assert_eq!(sql, r#""deleted_at" IS NULL"#);
2433 }
2434
2435 #[test]
2436 fn to_sql_quotes_comparison_operators() {
2437 use crate::dialect::Postgres;
2438
2439 let filter = Filter::Lt("age".into(), FilterValue::Int(18));
2440 let (sql, _) = filter.to_sql(0, &Postgres);
2441 assert!(sql.starts_with(r#""age" < "#), "Lt not quoted: {sql}");
2442
2443 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
2444 let (sql, _) = filter.to_sql(0, &Postgres);
2445 assert!(sql.starts_with(r#""price" <= "#), "Lte not quoted: {sql}");
2446
2447 let filter = Filter::Gt("score".into(), FilterValue::Int(0));
2448 let (sql, _) = filter.to_sql(0, &Postgres);
2449 assert!(sql.starts_with(r#""score" > "#), "Gt not quoted: {sql}");
2450
2451 let filter = Filter::Gte("quantity".into(), FilterValue::Int(1));
2452 let (sql, _) = filter.to_sql(0, &Postgres);
2453 assert!(
2454 sql.starts_with(r#""quantity" >= "#),
2455 "Gte not quoted: {sql}"
2456 );
2457
2458 let filter = Filter::NotEquals("status".into(), "deleted".into());
2459 let (sql, _) = filter.to_sql(0, &Postgres);
2460 assert!(
2461 sql.starts_with(r#""status" != "#),
2462 "NotEquals not quoted: {sql}"
2463 );
2464 }
2465
2466 #[test]
2467 fn to_sql_quotes_like_operators() {
2468 use crate::dialect::Postgres;
2469
2470 let filter = Filter::Contains("email".into(), "example".into());
2471 let (sql, _) = filter.to_sql(0, &Postgres);
2472 assert!(
2473 sql.starts_with(r#""email" LIKE "#),
2474 "Contains not quoted: {sql}"
2475 );
2476
2477 let filter = Filter::StartsWith("name".into(), "admin".into());
2478 let (sql, _) = filter.to_sql(0, &Postgres);
2479 assert!(
2480 sql.starts_with(r#""name" LIKE "#),
2481 "StartsWith not quoted: {sql}"
2482 );
2483
2484 let filter = Filter::EndsWith("domain".into(), ".com".into());
2485 let (sql, _) = filter.to_sql(0, &Postgres);
2486 assert!(
2487 sql.starts_with(r#""domain" LIKE "#),
2488 "EndsWith not quoted: {sql}"
2489 );
2490 }
2491
2492 #[test]
2493 fn to_sql_quotes_not_in() {
2494 use crate::dialect::Postgres;
2495 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
2496 let (sql, _) = filter.to_sql(0, &Postgres);
2497 assert!(
2498 sql.starts_with(r#""status" NOT IN ("#),
2499 "NotIn not quoted: {sql}"
2500 );
2501 }
2502
2503 #[test]
2504 fn to_sql_quotes_is_not_null() {
2505 use crate::dialect::Postgres;
2506 let filter = Filter::IsNotNull("verified_at".into());
2507 let (sql, _) = filter.to_sql(0, &Postgres);
2508 assert_eq!(sql, r#""verified_at" IS NOT NULL"#);
2509 }
2510
2511 #[test]
2512 fn filter_value_from_u64_in_range() {
2513 assert_eq!(FilterValue::from(42u64), FilterValue::Int(42));
2514 assert_eq!(FilterValue::from(0u64), FilterValue::Int(0));
2515 let max_safe = i64::MAX as u64;
2516 assert_eq!(FilterValue::from(max_safe), FilterValue::Int(i64::MAX));
2517 }
2518
2519 #[test]
2520 #[should_panic(expected = "u64 value exceeds i64::MAX")]
2521 fn filter_value_from_u64_overflow_panics() {
2522 let _ = FilterValue::from(u64::MAX);
2523 }
2524
2525 #[test]
2526 fn filter_value_from_chrono_datetime_utc_rfc3339() {
2527 use chrono::{TimeZone, Utc};
2528 let dt = Utc.with_ymd_and_hms(2020, 1, 15, 10, 30, 45).unwrap();
2529 let fv = FilterValue::from(dt);
2530 assert_eq!(
2531 fv,
2532 FilterValue::String("2020-01-15T10:30:45.000000Z".to_string())
2533 );
2534 }
2535
2536 #[test]
2537 fn filter_value_from_chrono_naive_datetime_iso() {
2538 use chrono::NaiveDate;
2539 let dt = NaiveDate::from_ymd_opt(2020, 1, 15)
2540 .unwrap()
2541 .and_hms_opt(10, 30, 45)
2542 .unwrap();
2543 let fv = FilterValue::from(dt);
2544 assert_eq!(
2545 fv,
2546 FilterValue::String("2020-01-15T10:30:45.000000".to_string())
2547 );
2548 }
2549
2550 #[test]
2551 fn filter_value_from_chrono_naive_date() {
2552 use chrono::NaiveDate;
2553 let d = NaiveDate::from_ymd_opt(2020, 1, 15).unwrap();
2554 assert_eq!(
2555 FilterValue::from(d),
2556 FilterValue::String("2020-01-15".to_string())
2557 );
2558 }
2559
2560 #[test]
2561 fn filter_value_from_chrono_naive_time() {
2562 use chrono::NaiveTime;
2563 let t = NaiveTime::from_hms_opt(10, 30, 45).unwrap();
2564 assert_eq!(
2565 FilterValue::from(t),
2566 FilterValue::String("10:30:45.000000".to_string())
2567 );
2568 }
2569
2570 #[test]
2577 fn filter_value_from_uuid_is_lowercase_hyphenated() {
2578 use uuid::Uuid;
2583 let u = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
2584 match FilterValue::from(u) {
2585 FilterValue::String(ref s) => {
2586 assert_eq!(s, "550e8400-e29b-41d4-a716-446655440000");
2587 assert_eq!(s, &u.to_string());
2588 }
2589 other => panic!("expected FilterValue::String, got {other:?}"),
2590 }
2591 }
2592
2593 #[test]
2594 fn filter_value_from_uuid_nil_round_trips() {
2595 use uuid::Uuid;
2596 let u = Uuid::nil();
2597 assert_eq!(
2598 FilterValue::from(u),
2599 FilterValue::String("00000000-0000-0000-0000-000000000000".to_string())
2600 );
2601 }
2602
2603 #[test]
2604 fn filter_value_from_decimal_uses_to_string_not_f64() {
2605 use rust_decimal::Decimal;
2609 use std::str::FromStr;
2610 let d = Decimal::from_str("3.14").unwrap();
2611 assert_eq!(
2612 FilterValue::from(d),
2613 FilterValue::String("3.14".to_string())
2614 );
2615 }
2616
2617 #[test]
2618 fn filter_value_from_decimal_high_precision_preserved() {
2619 use rust_decimal::Decimal;
2620 use std::str::FromStr;
2621 let d = Decimal::from_str("1234567890.1234567890").unwrap();
2623 match FilterValue::from(d) {
2624 FilterValue::String(ref s) => {
2625 assert_eq!(s, "1234567890.1234567890");
2626 }
2627 other => panic!("expected FilterValue::String, got {other:?}"),
2628 }
2629 }
2630
2631 #[test]
2632 fn filter_value_from_serde_json_value_keeps_json_variant() {
2633 let v = serde_json::json!({"key": "value", "nested": [1, 2, 3]});
2634 match FilterValue::from(v.clone()) {
2635 FilterValue::Json(inner) => {
2636 assert_eq!(inner, v);
2637 }
2638 other => panic!("expected FilterValue::Json, got {other:?}"),
2639 }
2640 }
2641
2642 #[test]
2643 fn filter_value_from_serde_json_null_keeps_json_variant() {
2644 let v = serde_json::Value::Null;
2649 match FilterValue::from(v) {
2650 FilterValue::Json(serde_json::Value::Null) => {}
2651 other => panic!("expected FilterValue::Json(Null), got {other:?}"),
2652 }
2653 }
2654
2655 #[test]
2656 fn filter_value_from_option_none_maps_to_null() {
2657 let none_i32: Option<i32> = None;
2660 assert_eq!(FilterValue::from(none_i32), FilterValue::Null);
2661 let none_string: Option<String> = None;
2662 assert_eq!(FilterValue::from(none_string), FilterValue::Null);
2663 }
2664
2665 #[test]
2666 fn filter_value_from_signed_integer_extremes() {
2667 assert_eq!(FilterValue::from(i8::MIN), FilterValue::Int(i8::MIN as i64));
2670 assert_eq!(FilterValue::from(i8::MAX), FilterValue::Int(i8::MAX as i64));
2671 assert_eq!(
2672 FilterValue::from(i16::MIN),
2673 FilterValue::Int(i16::MIN as i64)
2674 );
2675 assert_eq!(
2676 FilterValue::from(i16::MAX),
2677 FilterValue::Int(i16::MAX as i64)
2678 );
2679 }
2680
2681 #[test]
2682 fn filter_value_from_unsigned_integer_extremes() {
2683 assert_eq!(FilterValue::from(u8::MAX), FilterValue::Int(u8::MAX as i64));
2686 assert_eq!(
2687 FilterValue::from(u16::MAX),
2688 FilterValue::Int(u16::MAX as i64)
2689 );
2690 assert_eq!(
2691 FilterValue::from(u32::MAX),
2692 FilterValue::Int(u32::MAX as i64)
2693 );
2694 assert_eq!(FilterValue::from(u32::MAX), FilterValue::Int(4_294_967_295));
2696 }
2697
2698 #[test]
2699 fn filter_value_from_f32_widens_to_f64() {
2700 let v: f32 = 1.5;
2705 assert_eq!(FilterValue::from(v), FilterValue::Float(1.5));
2706 }
2707
2708 #[test]
2715 fn to_filter_value_option_some_some() {
2716 let v: Option<i32> = Some(42);
2717 assert_eq!(v.to_filter_value(), FilterValue::Int(42));
2718 }
2719
2720 #[test]
2721 fn to_filter_value_option_none_is_null() {
2722 let v: Option<i32> = None;
2723 assert_eq!(v.to_filter_value(), FilterValue::Null);
2724 }
2725
2726 #[test]
2727 fn to_filter_value_uuid_is_string() {
2728 let id = uuid::Uuid::nil();
2729 assert_eq!(id.to_filter_value(), FilterValue::String(id.to_string()));
2730 }
2731
2732 #[test]
2733 fn to_filter_value_bool_is_bool() {
2734 assert_eq!(true.to_filter_value(), FilterValue::Bool(true));
2735 }
2736}