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)]
554#[non_exhaustive]
555pub enum Filter {
556 #[default]
558 None,
559
560 Equals(FieldName, FilterValue),
562 NotEquals(FieldName, FilterValue),
564
565 Lt(FieldName, FilterValue),
567 Lte(FieldName, FilterValue),
569 Gt(FieldName, FilterValue),
571 Gte(FieldName, FilterValue),
573
574 In(FieldName, ValueList),
576 NotIn(FieldName, ValueList),
578
579 Contains(FieldName, FilterValue),
581 StartsWith(FieldName, FilterValue),
583 EndsWith(FieldName, FilterValue),
585
586 IsNull(FieldName),
588 IsNotNull(FieldName),
590
591 And(Box<[Filter]>),
596 Or(Box<[Filter]>),
601 Not(Box<Filter>),
603
604 ScalarSubquery {
614 sql: Cow<'static, str>,
619 params: Vec<FilterValue>,
621 },
622}
623
624impl Filter {
625 #[inline(always)]
627 pub fn none() -> Self {
628 Self::None
629 }
630
631 #[inline(always)]
633 pub fn is_none(&self) -> bool {
634 matches!(self, Self::None)
635 }
636
637 #[inline]
643 pub fn and(filters: impl IntoIterator<Item = Filter>) -> Self {
644 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
645 let count = filters.len();
646 let result = match count {
647 0 => Self::None,
648 1 => filters.into_iter().next().unwrap(),
649 _ => Self::And(filters.into_boxed_slice()),
650 };
651 debug!(count, "Filter::and() created");
652 result
653 }
654
655 #[inline(always)]
659 pub fn and2(a: Filter, b: Filter) -> Self {
660 match (a.is_none(), b.is_none()) {
661 (true, true) => Self::None,
662 (true, false) => b,
663 (false, true) => a,
664 (false, false) => Self::And(Box::new([a, b])),
665 }
666 }
667
668 #[inline]
674 pub fn or(filters: impl IntoIterator<Item = Filter>) -> Self {
675 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
676 let count = filters.len();
677 let result = match count {
678 0 => Self::None,
679 1 => filters.into_iter().next().unwrap(),
680 _ => Self::Or(filters.into_boxed_slice()),
681 };
682 debug!(count, "Filter::or() created");
683 result
684 }
685
686 #[inline(always)]
690 pub fn or2(a: Filter, b: Filter) -> Self {
691 match (a.is_none(), b.is_none()) {
692 (true, true) => Self::None,
693 (true, false) => b,
694 (false, true) => a,
695 (false, false) => Self::Or(Box::new([a, b])),
696 }
697 }
698
699 #[inline(always)]
720 pub fn and_n<const N: usize>(filters: [Filter; N]) -> Self {
721 Self::And(Box::new(filters))
723 }
724
725 #[inline(always)]
730 pub fn or_n<const N: usize>(filters: [Filter; N]) -> Self {
731 Self::Or(Box::new(filters))
732 }
733
734 #[inline(always)]
736 pub fn and3(a: Filter, b: Filter, c: Filter) -> Self {
737 Self::And(Box::new([a, b, c]))
738 }
739
740 #[inline(always)]
742 pub fn and4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
743 Self::And(Box::new([a, b, c, d]))
744 }
745
746 #[inline(always)]
748 pub fn and5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
749 Self::And(Box::new([a, b, c, d, e]))
750 }
751
752 #[inline(always)]
754 pub fn or3(a: Filter, b: Filter, c: Filter) -> Self {
755 Self::Or(Box::new([a, b, c]))
756 }
757
758 #[inline(always)]
760 pub fn or4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
761 Self::Or(Box::new([a, b, c, d]))
762 }
763
764 #[inline(always)]
766 pub fn or5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
767 Self::Or(Box::new([a, b, c, d, e]))
768 }
769
770 #[inline]
779 pub fn in_i64(field: impl Into<FieldName>, values: impl IntoIterator<Item = i64>) -> Self {
780 let list: ValueList = values.into_iter().map(FilterValue::Int).collect();
781 Self::In(field.into(), list)
782 }
783
784 #[inline]
786 pub fn in_i32(field: impl Into<FieldName>, values: impl IntoIterator<Item = i32>) -> Self {
787 let list: ValueList = values
788 .into_iter()
789 .map(|v| FilterValue::Int(v as i64))
790 .collect();
791 Self::In(field.into(), list)
792 }
793
794 #[inline]
796 pub fn in_strings(
797 field: impl Into<FieldName>,
798 values: impl IntoIterator<Item = String>,
799 ) -> Self {
800 let list: ValueList = values.into_iter().map(FilterValue::String).collect();
801 Self::In(field.into(), list)
802 }
803
804 #[inline]
808 pub fn in_values(field: impl Into<FieldName>, values: ValueList) -> Self {
809 Self::In(field.into(), values)
810 }
811
812 #[inline]
816 pub fn in_range(field: impl Into<FieldName>, range: std::ops::Range<i64>) -> Self {
817 let list: ValueList = range.map(FilterValue::Int).collect();
818 Self::In(field.into(), list)
819 }
820
821 #[inline(always)]
826 pub fn in_i64_slice(field: impl Into<FieldName>, values: &[i64]) -> Self {
827 let mut list = Vec::with_capacity(values.len());
828 for &v in values {
829 list.push(FilterValue::Int(v));
830 }
831 Self::In(field.into(), list)
832 }
833
834 #[inline(always)]
836 pub fn in_i32_slice(field: impl Into<FieldName>, values: &[i32]) -> Self {
837 let mut list = Vec::with_capacity(values.len());
838 for &v in values {
839 list.push(FilterValue::Int(v as i64));
840 }
841 Self::In(field.into(), list)
842 }
843
844 #[inline(always)]
846 pub fn in_str_slice(field: impl Into<FieldName>, values: &[&str]) -> Self {
847 let mut list = Vec::with_capacity(values.len());
848 for &v in values {
849 list.push(FilterValue::String(v.to_string()));
850 }
851 Self::In(field.into(), list)
852 }
853
854 #[inline]
856 #[allow(clippy::should_implement_trait)]
857 pub fn not(filter: Filter) -> Self {
858 if filter.is_none() {
859 return Self::None;
860 }
861 Self::Not(Box::new(filter))
862 }
863
864 #[inline]
878 pub fn in_slice<T: Into<FilterValue> + Clone>(
879 field: impl Into<FieldName>,
880 values: &[T],
881 ) -> Self {
882 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
883 Self::In(field.into(), list)
884 }
885
886 #[inline]
897 pub fn not_in_slice<T: Into<FilterValue> + Clone>(
898 field: impl Into<FieldName>,
899 values: &[T],
900 ) -> Self {
901 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
902 Self::NotIn(field.into(), list)
903 }
904
905 #[inline]
917 pub fn in_array<T: Into<FilterValue>, const N: usize>(
918 field: impl Into<FieldName>,
919 values: [T; N],
920 ) -> Self {
921 let list: ValueList = values.into_iter().map(Into::into).collect();
922 Self::In(field.into(), list)
923 }
924
925 #[inline]
927 pub fn not_in_array<T: Into<FilterValue>, const N: usize>(
928 field: impl Into<FieldName>,
929 values: [T; N],
930 ) -> Self {
931 let list: ValueList = values.into_iter().map(Into::into).collect();
932 Self::NotIn(field.into(), list)
933 }
934
935 pub fn and_then(self, other: Filter) -> Self {
937 if self.is_none() {
938 return other;
939 }
940 if other.is_none() {
941 return self;
942 }
943 match self {
944 Self::And(filters) => {
945 let mut vec: Vec<_> = filters.into_vec();
947 vec.push(other);
948 Self::And(vec.into_boxed_slice())
949 }
950 _ => Self::And(Box::new([self, other])),
951 }
952 }
953
954 pub fn or_else(self, other: Filter) -> Self {
956 if self.is_none() {
957 return other;
958 }
959 if other.is_none() {
960 return self;
961 }
962 match self {
963 Self::Or(filters) => {
964 let mut vec: Vec<_> = filters.into_vec();
966 vec.push(other);
967 Self::Or(vec.into_boxed_slice())
968 }
969 _ => Self::Or(Box::new([self, other])),
970 }
971 }
972
973 pub fn to_sql(
976 &self,
977 param_offset: usize,
978 dialect: &dyn crate::dialect::SqlDialect,
979 ) -> (String, Vec<FilterValue>) {
980 let mut params = Vec::new();
981 let sql = self.to_sql_with_params(param_offset, &mut params, dialect);
982 (sql, params)
983 }
984
985 fn to_sql_with_params(
1006 &self,
1007 param_idx: usize,
1008 params: &mut Vec<FilterValue>,
1009 dialect: &dyn crate::dialect::SqlDialect,
1010 ) -> String {
1011 match self {
1012 Self::None => "TRUE".to_string(),
1013
1014 Self::Equals(col, val) => {
1015 let c = dialect.quote_ident(col);
1016 if val.is_null() {
1017 format!("{} IS NULL", c)
1018 } else {
1019 params.push(val.clone());
1020 format!("{} = {}", c, dialect.placeholder(param_idx + 1))
1021 }
1022 }
1023 Self::NotEquals(col, val) => {
1024 let c = dialect.quote_ident(col);
1025 if val.is_null() {
1026 format!("{} IS NOT NULL", c)
1027 } else {
1028 params.push(val.clone());
1029 format!("{} != {}", c, dialect.placeholder(param_idx + 1))
1030 }
1031 }
1032
1033 Self::Lt(col, val) => {
1034 let c = dialect.quote_ident(col);
1035 params.push(val.clone());
1036 format!("{} < {}", c, dialect.placeholder(param_idx + 1))
1037 }
1038 Self::Lte(col, val) => {
1039 let c = dialect.quote_ident(col);
1040 params.push(val.clone());
1041 format!("{} <= {}", c, dialect.placeholder(param_idx + 1))
1042 }
1043 Self::Gt(col, val) => {
1044 let c = dialect.quote_ident(col);
1045 params.push(val.clone());
1046 format!("{} > {}", c, dialect.placeholder(param_idx + 1))
1047 }
1048 Self::Gte(col, val) => {
1049 let c = dialect.quote_ident(col);
1050 params.push(val.clone());
1051 format!("{} >= {}", c, dialect.placeholder(param_idx + 1))
1052 }
1053
1054 Self::In(col, values) => {
1055 if values.is_empty() {
1056 return "FALSE".to_string();
1057 }
1058 let c = dialect.quote_ident(col);
1059 let placeholders: Vec<_> = values
1060 .iter()
1061 .enumerate()
1062 .map(|(i, v)| {
1063 params.push(v.clone());
1064 dialect.placeholder(param_idx + i + 1)
1065 })
1066 .collect();
1067 format!("{} IN ({})", c, placeholders.join(", "))
1068 }
1069 Self::NotIn(col, values) => {
1070 if values.is_empty() {
1071 return "TRUE".to_string();
1072 }
1073 let c = dialect.quote_ident(col);
1074 let placeholders: Vec<_> = values
1075 .iter()
1076 .enumerate()
1077 .map(|(i, v)| {
1078 params.push(v.clone());
1079 dialect.placeholder(param_idx + i + 1)
1080 })
1081 .collect();
1082 format!("{} NOT IN ({})", c, placeholders.join(", "))
1083 }
1084
1085 Self::Contains(col, val) => {
1086 let c = dialect.quote_ident(col);
1087 if let FilterValue::String(s) = val {
1088 params.push(FilterValue::String(format!("%{}%", s)));
1089 } else {
1090 params.push(val.clone());
1091 }
1092 format!("{} LIKE {}", c, dialect.placeholder(param_idx + 1))
1093 }
1094 Self::StartsWith(col, val) => {
1095 let c = dialect.quote_ident(col);
1096 if let FilterValue::String(s) = val {
1097 params.push(FilterValue::String(format!("{}%", s)));
1098 } else {
1099 params.push(val.clone());
1100 }
1101 format!("{} LIKE {}", c, dialect.placeholder(param_idx + 1))
1102 }
1103 Self::EndsWith(col, val) => {
1104 let c = dialect.quote_ident(col);
1105 if let FilterValue::String(s) = val {
1106 params.push(FilterValue::String(format!("%{}", s)));
1107 } else {
1108 params.push(val.clone());
1109 }
1110 format!("{} LIKE {}", c, dialect.placeholder(param_idx + 1))
1111 }
1112
1113 Self::IsNull(col) => {
1114 let c = dialect.quote_ident(col);
1115 format!("{} IS NULL", c)
1116 }
1117 Self::IsNotNull(col) => {
1118 let c = dialect.quote_ident(col);
1119 format!("{} IS NOT NULL", c)
1120 }
1121
1122 Self::And(filters) => {
1123 if filters.is_empty() {
1124 return "TRUE".to_string();
1125 }
1126 let base = param_idx - params.len();
1131 let parts: Vec<_> = filters
1132 .iter()
1133 .map(|f| f.to_sql_with_params(base + params.len(), params, dialect))
1134 .collect();
1135 format!("({})", parts.join(" AND "))
1136 }
1137 Self::Or(filters) => {
1138 if filters.is_empty() {
1139 return "FALSE".to_string();
1140 }
1141 let base = param_idx - params.len();
1142 let parts: Vec<_> = filters
1143 .iter()
1144 .map(|f| f.to_sql_with_params(base + params.len(), params, dialect))
1145 .collect();
1146 format!("({})", parts.join(" OR "))
1147 }
1148 Self::Not(filter) => {
1149 let inner = filter.to_sql_with_params(param_idx, params, dialect);
1150 format!("NOT ({})", inner)
1151 }
1152
1153 Self::ScalarSubquery {
1154 sql,
1155 params: inner_params,
1156 } => {
1157 let base = param_idx;
1162 for v in inner_params.iter() {
1166 params.push(v.clone());
1167 }
1168 let mut out = String::with_capacity(sql.len() + inner_params.len() * 4);
1169 let mut chars = sql.chars().peekable();
1170 while let Some(ch) = chars.next() {
1171 if ch == '{' {
1172 let mut digits = String::new();
1174 while let Some(&c) = chars.peek() {
1175 if c == '}' {
1176 chars.next();
1177 break;
1178 }
1179 digits.push(c);
1180 chars.next();
1181 }
1182 let n: usize = digits.parse().unwrap_or_else(|_| {
1183 panic!(
1184 "Filter::ScalarSubquery: invalid placeholder index `{{{}}}`",
1185 digits
1186 )
1187 });
1188 if n >= inner_params.len() {
1189 panic!(
1190 "Filter::ScalarSubquery: placeholder {{{}}} out of range (have {} params)",
1191 n,
1192 inner_params.len()
1193 );
1194 }
1195 out.push_str(&dialect.placeholder(base + n + 1));
1196 } else {
1197 out.push(ch);
1198 }
1199 }
1200 format!("({})", out)
1201 }
1202 }
1203 }
1204
1205 #[inline]
1223 pub fn and_builder(capacity: usize) -> AndFilterBuilder {
1224 AndFilterBuilder::with_capacity(capacity)
1225 }
1226
1227 #[inline]
1244 pub fn or_builder(capacity: usize) -> OrFilterBuilder {
1245 OrFilterBuilder::with_capacity(capacity)
1246 }
1247
1248 #[inline]
1264 pub fn builder() -> FluentFilterBuilder {
1265 FluentFilterBuilder::new()
1266 }
1267}
1268
1269#[derive(Debug, Clone)]
1273pub struct AndFilterBuilder {
1274 filters: Vec<Filter>,
1275}
1276
1277impl AndFilterBuilder {
1278 #[inline]
1280 pub fn new() -> Self {
1281 Self {
1282 filters: Vec::new(),
1283 }
1284 }
1285
1286 #[inline]
1288 pub fn with_capacity(capacity: usize) -> Self {
1289 Self {
1290 filters: Vec::with_capacity(capacity),
1291 }
1292 }
1293
1294 #[inline]
1296 pub fn push(mut self, filter: Filter) -> Self {
1297 if !filter.is_none() {
1298 self.filters.push(filter);
1299 }
1300 self
1301 }
1302
1303 #[inline]
1305 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1306 self.filters
1307 .extend(filters.into_iter().filter(|f| !f.is_none()));
1308 self
1309 }
1310
1311 #[inline]
1313 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1314 if condition { self.push(filter) } else { self }
1315 }
1316
1317 #[inline]
1319 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1320 where
1321 F: Into<Filter>,
1322 {
1323 match opt {
1324 Some(f) => self.push(f.into()),
1325 None => self,
1326 }
1327 }
1328
1329 #[inline]
1331 pub fn build(self) -> Filter {
1332 match self.filters.len() {
1333 0 => Filter::None,
1334 1 => self.filters.into_iter().next().unwrap(),
1335 _ => Filter::And(self.filters.into_boxed_slice()),
1336 }
1337 }
1338
1339 #[inline]
1341 pub fn len(&self) -> usize {
1342 self.filters.len()
1343 }
1344
1345 #[inline]
1347 pub fn is_empty(&self) -> bool {
1348 self.filters.is_empty()
1349 }
1350}
1351
1352impl Default for AndFilterBuilder {
1353 fn default() -> Self {
1354 Self::new()
1355 }
1356}
1357
1358#[derive(Debug, Clone)]
1362pub struct OrFilterBuilder {
1363 filters: Vec<Filter>,
1364}
1365
1366impl OrFilterBuilder {
1367 #[inline]
1369 pub fn new() -> Self {
1370 Self {
1371 filters: Vec::new(),
1372 }
1373 }
1374
1375 #[inline]
1377 pub fn with_capacity(capacity: usize) -> Self {
1378 Self {
1379 filters: Vec::with_capacity(capacity),
1380 }
1381 }
1382
1383 #[inline]
1385 pub fn push(mut self, filter: Filter) -> Self {
1386 if !filter.is_none() {
1387 self.filters.push(filter);
1388 }
1389 self
1390 }
1391
1392 #[inline]
1394 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1395 self.filters
1396 .extend(filters.into_iter().filter(|f| !f.is_none()));
1397 self
1398 }
1399
1400 #[inline]
1402 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1403 if condition { self.push(filter) } else { self }
1404 }
1405
1406 #[inline]
1408 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1409 where
1410 F: Into<Filter>,
1411 {
1412 match opt {
1413 Some(f) => self.push(f.into()),
1414 None => self,
1415 }
1416 }
1417
1418 #[inline]
1420 pub fn build(self) -> Filter {
1421 match self.filters.len() {
1422 0 => Filter::None,
1423 1 => self.filters.into_iter().next().unwrap(),
1424 _ => Filter::Or(self.filters.into_boxed_slice()),
1425 }
1426 }
1427
1428 #[inline]
1430 pub fn len(&self) -> usize {
1431 self.filters.len()
1432 }
1433
1434 #[inline]
1436 pub fn is_empty(&self) -> bool {
1437 self.filters.is_empty()
1438 }
1439}
1440
1441impl Default for OrFilterBuilder {
1442 fn default() -> Self {
1443 Self::new()
1444 }
1445}
1446
1447#[derive(Debug, Clone)]
1472pub struct FluentFilterBuilder {
1473 filters: Vec<Filter>,
1474}
1475
1476impl FluentFilterBuilder {
1477 #[inline]
1479 pub fn new() -> Self {
1480 Self {
1481 filters: Vec::new(),
1482 }
1483 }
1484
1485 #[inline]
1487 pub fn with_capacity(mut self, capacity: usize) -> Self {
1488 self.filters.reserve(capacity);
1489 self
1490 }
1491
1492 #[inline]
1494 pub fn eq<F, V>(mut self, field: F, value: V) -> Self
1495 where
1496 F: Into<FieldName>,
1497 V: Into<FilterValue>,
1498 {
1499 self.filters
1500 .push(Filter::Equals(field.into(), value.into()));
1501 self
1502 }
1503
1504 #[inline]
1506 pub fn ne<F, V>(mut self, field: F, value: V) -> Self
1507 where
1508 F: Into<FieldName>,
1509 V: Into<FilterValue>,
1510 {
1511 self.filters
1512 .push(Filter::NotEquals(field.into(), value.into()));
1513 self
1514 }
1515
1516 #[inline]
1518 pub fn lt<F, V>(mut self, field: F, value: V) -> Self
1519 where
1520 F: Into<FieldName>,
1521 V: Into<FilterValue>,
1522 {
1523 self.filters.push(Filter::Lt(field.into(), value.into()));
1524 self
1525 }
1526
1527 #[inline]
1529 pub fn lte<F, V>(mut self, field: F, value: V) -> Self
1530 where
1531 F: Into<FieldName>,
1532 V: Into<FilterValue>,
1533 {
1534 self.filters.push(Filter::Lte(field.into(), value.into()));
1535 self
1536 }
1537
1538 #[inline]
1540 pub fn gt<F, V>(mut self, field: F, value: V) -> Self
1541 where
1542 F: Into<FieldName>,
1543 V: Into<FilterValue>,
1544 {
1545 self.filters.push(Filter::Gt(field.into(), value.into()));
1546 self
1547 }
1548
1549 #[inline]
1551 pub fn gte<F, V>(mut self, field: F, value: V) -> Self
1552 where
1553 F: Into<FieldName>,
1554 V: Into<FilterValue>,
1555 {
1556 self.filters.push(Filter::Gte(field.into(), value.into()));
1557 self
1558 }
1559
1560 #[inline]
1562 pub fn is_in<F, I, V>(mut self, field: F, values: I) -> Self
1563 where
1564 F: Into<FieldName>,
1565 I: IntoIterator<Item = V>,
1566 V: Into<FilterValue>,
1567 {
1568 self.filters.push(Filter::In(
1569 field.into(),
1570 values.into_iter().map(Into::into).collect(),
1571 ));
1572 self
1573 }
1574
1575 #[inline]
1577 pub fn not_in<F, I, V>(mut self, field: F, values: I) -> Self
1578 where
1579 F: Into<FieldName>,
1580 I: IntoIterator<Item = V>,
1581 V: Into<FilterValue>,
1582 {
1583 self.filters.push(Filter::NotIn(
1584 field.into(),
1585 values.into_iter().map(Into::into).collect(),
1586 ));
1587 self
1588 }
1589
1590 #[inline]
1592 pub fn contains<F, V>(mut self, field: F, value: V) -> Self
1593 where
1594 F: Into<FieldName>,
1595 V: Into<FilterValue>,
1596 {
1597 self.filters
1598 .push(Filter::Contains(field.into(), value.into()));
1599 self
1600 }
1601
1602 #[inline]
1604 pub fn starts_with<F, V>(mut self, field: F, value: V) -> Self
1605 where
1606 F: Into<FieldName>,
1607 V: Into<FilterValue>,
1608 {
1609 self.filters
1610 .push(Filter::StartsWith(field.into(), value.into()));
1611 self
1612 }
1613
1614 #[inline]
1616 pub fn ends_with<F, V>(mut self, field: F, value: V) -> Self
1617 where
1618 F: Into<FieldName>,
1619 V: Into<FilterValue>,
1620 {
1621 self.filters
1622 .push(Filter::EndsWith(field.into(), value.into()));
1623 self
1624 }
1625
1626 #[inline]
1628 pub fn is_null<F>(mut self, field: F) -> Self
1629 where
1630 F: Into<FieldName>,
1631 {
1632 self.filters.push(Filter::IsNull(field.into()));
1633 self
1634 }
1635
1636 #[inline]
1638 pub fn is_not_null<F>(mut self, field: F) -> Self
1639 where
1640 F: Into<FieldName>,
1641 {
1642 self.filters.push(Filter::IsNotNull(field.into()));
1643 self
1644 }
1645
1646 #[inline]
1648 pub fn filter(mut self, filter: Filter) -> Self {
1649 if !filter.is_none() {
1650 self.filters.push(filter);
1651 }
1652 self
1653 }
1654
1655 #[inline]
1657 pub fn filter_if(self, condition: bool, filter: Filter) -> Self {
1658 if condition { self.filter(filter) } else { self }
1659 }
1660
1661 #[inline]
1663 pub fn filter_if_some<F>(self, opt: Option<F>) -> Self
1664 where
1665 F: Into<Filter>,
1666 {
1667 match opt {
1668 Some(f) => self.filter(f.into()),
1669 None => self,
1670 }
1671 }
1672
1673 #[inline]
1675 pub fn build_and(self) -> Filter {
1676 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1677 match filters.len() {
1678 0 => Filter::None,
1679 1 => filters.into_iter().next().unwrap(),
1680 _ => Filter::And(filters.into_boxed_slice()),
1681 }
1682 }
1683
1684 #[inline]
1686 pub fn build_or(self) -> Filter {
1687 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1688 match filters.len() {
1689 0 => Filter::None,
1690 1 => filters.into_iter().next().unwrap(),
1691 _ => Filter::Or(filters.into_boxed_slice()),
1692 }
1693 }
1694
1695 #[inline]
1697 pub fn len(&self) -> usize {
1698 self.filters.len()
1699 }
1700
1701 #[inline]
1703 pub fn is_empty(&self) -> bool {
1704 self.filters.is_empty()
1705 }
1706}
1707
1708impl Default for FluentFilterBuilder {
1709 fn default() -> Self {
1710 Self::new()
1711 }
1712}
1713
1714#[cfg(test)]
1715mod tests {
1716 use super::*;
1717
1718 #[test]
1719 fn test_filter_value_from() {
1720 assert_eq!(FilterValue::from(42i32), FilterValue::Int(42));
1721 assert_eq!(
1722 FilterValue::from("hello"),
1723 FilterValue::String("hello".to_string())
1724 );
1725 assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
1726 }
1727
1728 #[test]
1729 fn test_scalar_filter_equals() {
1730 let filter = ScalarFilter::Equals("test@example.com".to_string()).into_filter("email");
1731
1732 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1733 assert_eq!(sql, r#""email" = $1"#);
1734 assert_eq!(params.len(), 1);
1735 }
1736
1737 #[test]
1738 fn test_filter_and() {
1739 let f1 = Filter::Equals("name".into(), "Alice".into());
1740 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1741 let combined = Filter::and([f1, f2]);
1742
1743 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
1744 assert!(sql.contains("AND"));
1745 assert_eq!(params.len(), 2);
1746 }
1747
1748 #[test]
1749 fn test_filter_or() {
1750 let f1 = Filter::Equals("status".into(), "active".into());
1751 let f2 = Filter::Equals("status".into(), "pending".into());
1752 let combined = Filter::or([f1, f2]);
1753
1754 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
1755 assert!(sql.contains("OR"));
1756 }
1757
1758 #[test]
1759 fn test_filter_not() {
1760 let filter = Filter::not(Filter::Equals("deleted".into(), FilterValue::Bool(true)));
1761
1762 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
1763 assert!(sql.contains("NOT"));
1764 }
1765
1766 #[test]
1767 fn test_filter_is_null() {
1768 let filter = Filter::IsNull("deleted_at".into());
1769 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1770 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1771 assert!(params.is_empty());
1772 }
1773
1774 #[test]
1775 fn test_filter_in() {
1776 let filter = Filter::In("status".into(), vec!["active".into(), "pending".into()]);
1777 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1778 assert!(sql.contains("IN"));
1779 assert_eq!(params.len(), 2);
1780 }
1781
1782 #[test]
1783 fn test_filter_contains() {
1784 let filter = Filter::Contains("email".into(), "example".into());
1785 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1786 assert!(sql.contains("LIKE"));
1787 assert_eq!(params.len(), 1);
1788 if let FilterValue::String(s) = ¶ms[0] {
1789 assert!(s.contains("%example%"));
1790 }
1791 }
1792
1793 #[test]
1796 fn test_filter_value_is_null() {
1797 assert!(FilterValue::Null.is_null());
1798 assert!(!FilterValue::Bool(false).is_null());
1799 assert!(!FilterValue::Int(0).is_null());
1800 assert!(!FilterValue::Float(0.0).is_null());
1801 assert!(!FilterValue::String("".to_string()).is_null());
1802 }
1803
1804 #[test]
1805 fn test_filter_value_from_i64() {
1806 assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
1807 assert_eq!(FilterValue::from(-100i64), FilterValue::Int(-100));
1808 }
1809
1810 #[test]
1811 #[allow(clippy::approx_constant)]
1812 fn test_filter_value_from_f64() {
1813 assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1814 }
1815
1816 #[test]
1817 fn test_filter_value_from_string() {
1818 assert_eq!(
1819 FilterValue::from("hello".to_string()),
1820 FilterValue::String("hello".to_string())
1821 );
1822 }
1823
1824 #[test]
1825 fn test_filter_value_from_vec() {
1826 let values: Vec<i32> = vec![1, 2, 3];
1827 let filter_val: FilterValue = values.into();
1828 if let FilterValue::List(list) = filter_val {
1829 assert_eq!(list.len(), 3);
1830 assert_eq!(list[0], FilterValue::Int(1));
1831 assert_eq!(list[1], FilterValue::Int(2));
1832 assert_eq!(list[2], FilterValue::Int(3));
1833 } else {
1834 panic!("Expected List");
1835 }
1836 }
1837
1838 #[test]
1839 fn test_filter_value_from_option_some() {
1840 let val: FilterValue = Some(42i32).into();
1841 assert_eq!(val, FilterValue::Int(42));
1842 }
1843
1844 #[test]
1845 fn test_filter_value_from_option_none() {
1846 let val: FilterValue = Option::<i32>::None.into();
1847 assert_eq!(val, FilterValue::Null);
1848 }
1849
1850 #[test]
1853 fn test_scalar_filter_not() {
1854 let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1855 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1856 assert_eq!(sql, r#""name" != $1"#);
1857 assert_eq!(params.len(), 1);
1858 }
1859
1860 #[test]
1861 fn test_scalar_filter_in() {
1862 let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1863 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1864 assert!(sql.contains("IN"));
1865 assert_eq!(params.len(), 2);
1866 }
1867
1868 #[test]
1869 fn test_scalar_filter_not_in() {
1870 let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1871 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1872 assert!(sql.contains("NOT IN"));
1873 assert_eq!(params.len(), 1);
1874 }
1875
1876 #[test]
1877 fn test_scalar_filter_lt() {
1878 let filter = ScalarFilter::Lt(100i32).into_filter("price");
1879 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1880 assert_eq!(sql, r#""price" < $1"#);
1881 assert_eq!(params.len(), 1);
1882 }
1883
1884 #[test]
1885 fn test_scalar_filter_lte() {
1886 let filter = ScalarFilter::Lte(100i32).into_filter("price");
1887 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1888 assert_eq!(sql, r#""price" <= $1"#);
1889 assert_eq!(params.len(), 1);
1890 }
1891
1892 #[test]
1893 fn test_scalar_filter_gt() {
1894 let filter = ScalarFilter::Gt(0i32).into_filter("quantity");
1895 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1896 assert_eq!(sql, r#""quantity" > $1"#);
1897 assert_eq!(params.len(), 1);
1898 }
1899
1900 #[test]
1901 fn test_scalar_filter_gte() {
1902 let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1903 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1904 assert_eq!(sql, r#""quantity" >= $1"#);
1905 assert_eq!(params.len(), 1);
1906 }
1907
1908 #[test]
1909 fn test_scalar_filter_starts_with() {
1910 let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1911 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1912 assert!(sql.contains("LIKE"));
1913 assert_eq!(params.len(), 1);
1914 if let FilterValue::String(s) = ¶ms[0] {
1915 assert!(s.starts_with("prefix"));
1916 assert!(s.ends_with("%"));
1917 }
1918 }
1919
1920 #[test]
1921 fn test_scalar_filter_ends_with() {
1922 let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
1923 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1924 assert!(sql.contains("LIKE"));
1925 assert_eq!(params.len(), 1);
1926 if let FilterValue::String(s) = ¶ms[0] {
1927 assert!(s.starts_with("%"));
1928 assert!(s.ends_with("suffix"));
1929 }
1930 }
1931
1932 #[test]
1933 fn test_scalar_filter_is_null() {
1934 let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
1935 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1936 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1937 assert!(params.is_empty());
1938 }
1939
1940 #[test]
1941 fn test_scalar_filter_is_not_null() {
1942 let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
1943 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1944 assert_eq!(sql, r#""name" IS NOT NULL"#);
1945 assert!(params.is_empty());
1946 }
1947
1948 #[test]
1951 fn test_filter_none() {
1952 let filter = Filter::none();
1953 assert!(filter.is_none());
1954 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1955 assert_eq!(sql, "TRUE"); assert!(params.is_empty());
1957 }
1958
1959 #[test]
1960 fn test_filter_not_equals() {
1961 let filter = Filter::NotEquals("status".into(), "deleted".into());
1962 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1963 assert_eq!(sql, r#""status" != $1"#);
1964 assert_eq!(params.len(), 1);
1965 }
1966
1967 #[test]
1968 fn test_filter_lte() {
1969 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
1970 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1971 assert_eq!(sql, r#""price" <= $1"#);
1972 assert_eq!(params.len(), 1);
1973 }
1974
1975 #[test]
1976 fn test_filter_gte() {
1977 let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
1978 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1979 assert_eq!(sql, r#""quantity" >= $1"#);
1980 assert_eq!(params.len(), 1);
1981 }
1982
1983 #[test]
1984 fn test_filter_not_in() {
1985 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
1986 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1987 assert!(sql.contains("NOT IN"));
1988 assert_eq!(params.len(), 2);
1989 }
1990
1991 #[test]
1992 fn test_filter_starts_with() {
1993 let filter = Filter::StartsWith("email".into(), "admin".into());
1994 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1995 assert!(sql.contains("LIKE"));
1996 assert_eq!(params.len(), 1);
1997 }
1998
1999 #[test]
2000 fn test_filter_ends_with() {
2001 let filter = Filter::EndsWith("email".into(), "@example.com".into());
2002 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2003 assert!(sql.contains("LIKE"));
2004 assert_eq!(params.len(), 1);
2005 }
2006
2007 #[test]
2008 fn test_filter_is_not_null() {
2009 let filter = Filter::IsNotNull("name".into());
2010 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2011 assert_eq!(sql, r#""name" IS NOT NULL"#);
2012 assert!(params.is_empty());
2013 }
2014
2015 #[test]
2018 fn test_filter_and_empty() {
2019 let filter = Filter::and([]);
2020 assert!(filter.is_none());
2021 }
2022
2023 #[test]
2024 fn test_filter_and_single() {
2025 let f = Filter::Equals("name".into(), "Alice".into());
2026 let combined = Filter::and([f.clone()]);
2027 assert_eq!(combined, f);
2028 }
2029
2030 #[test]
2031 fn test_filter_and_with_none() {
2032 let f1 = Filter::Equals("name".into(), "Alice".into());
2033 let f2 = Filter::None;
2034 let combined = Filter::and([f1.clone(), f2]);
2035 assert_eq!(combined, f1);
2036 }
2037
2038 #[test]
2039 fn test_filter_or_empty() {
2040 let filter = Filter::or([]);
2041 assert!(filter.is_none());
2042 }
2043
2044 #[test]
2045 fn test_filter_or_single() {
2046 let f = Filter::Equals("status".into(), "active".into());
2047 let combined = Filter::or([f.clone()]);
2048 assert_eq!(combined, f);
2049 }
2050
2051 #[test]
2052 fn test_filter_or_with_none() {
2053 let f1 = Filter::Equals("status".into(), "active".into());
2054 let f2 = Filter::None;
2055 let combined = Filter::or([f1.clone(), f2]);
2056 assert_eq!(combined, f1);
2057 }
2058
2059 #[test]
2060 fn test_filter_not_none() {
2061 let filter = Filter::not(Filter::None);
2062 assert!(filter.is_none());
2063 }
2064
2065 #[test]
2066 fn test_filter_and_then() {
2067 let f1 = Filter::Equals("name".into(), "Alice".into());
2068 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
2069 let combined = f1.and_then(f2);
2070
2071 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2072 assert!(sql.contains("AND"));
2073 assert_eq!(params.len(), 2);
2074 }
2075
2076 #[test]
2077 fn test_filter_and_then_with_none_first() {
2078 let f1 = Filter::None;
2079 let f2 = Filter::Equals("name".into(), "Bob".into());
2080 let combined = f1.and_then(f2.clone());
2081 assert_eq!(combined, f2);
2082 }
2083
2084 #[test]
2085 fn test_filter_and_then_with_none_second() {
2086 let f1 = Filter::Equals("name".into(), "Alice".into());
2087 let f2 = Filter::None;
2088 let combined = f1.clone().and_then(f2);
2089 assert_eq!(combined, f1);
2090 }
2091
2092 #[test]
2093 fn test_filter_and_then_chained() {
2094 let f1 = Filter::Equals("a".into(), "1".into());
2095 let f2 = Filter::Equals("b".into(), "2".into());
2096 let f3 = Filter::Equals("c".into(), "3".into());
2097 let combined = f1.and_then(f2).and_then(f3);
2098
2099 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2100 assert!(sql.contains("AND"));
2101 assert_eq!(params.len(), 3);
2102 }
2103
2104 #[test]
2105 fn test_filter_or_else() {
2106 let f1 = Filter::Equals("status".into(), "active".into());
2107 let f2 = Filter::Equals("status".into(), "pending".into());
2108 let combined = f1.or_else(f2);
2109
2110 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
2111 assert!(sql.contains("OR"));
2112 }
2113
2114 #[test]
2115 fn test_filter_or_else_with_none_first() {
2116 let f1 = Filter::None;
2117 let f2 = Filter::Equals("name".into(), "Bob".into());
2118 let combined = f1.or_else(f2.clone());
2119 assert_eq!(combined, f2);
2120 }
2121
2122 #[test]
2123 fn test_filter_or_else_with_none_second() {
2124 let f1 = Filter::Equals("name".into(), "Alice".into());
2125 let f2 = Filter::None;
2126 let combined = f1.clone().or_else(f2);
2127 assert_eq!(combined, f1);
2128 }
2129
2130 #[test]
2133 fn test_filter_nested_and_or() {
2134 let f1 = Filter::Equals("status".into(), "active".into());
2135 let f2 = Filter::and([
2136 Filter::Gt("age".into(), FilterValue::Int(18)),
2137 Filter::Lt("age".into(), FilterValue::Int(65)),
2138 ]);
2139 let combined = Filter::and([f1, f2]);
2140
2141 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2142 assert!(sql.contains("AND"));
2143 assert_eq!(params.len(), 3);
2144 }
2145
2146 #[test]
2147 fn test_filter_nested_not() {
2148 let inner = Filter::and([
2149 Filter::Equals("status".into(), "deleted".into()),
2150 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2151 ]);
2152 let filter = Filter::not(inner);
2153
2154 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2155 assert!(sql.contains("NOT"));
2156 assert!(sql.contains("AND"));
2157 assert_eq!(params.len(), 2);
2158 }
2159
2160 #[test]
2161 fn test_filter_with_json_value() {
2162 let json_val = serde_json::json!({"key": "value"});
2163 let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
2164 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2165 assert_eq!(sql, r#""metadata" = $1"#);
2166 assert_eq!(params.len(), 1);
2167 }
2168
2169 #[test]
2170 fn test_filter_in_empty_list() {
2171 let filter = Filter::In("status".into(), vec![]);
2172 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2173 assert!(
2175 sql.contains("FALSE")
2176 || sql.contains("1=0")
2177 || sql.is_empty()
2178 || sql.contains("status")
2179 );
2180 assert!(params.is_empty());
2181 }
2182
2183 #[test]
2184 fn test_filter_with_null_value() {
2185 let filter = Filter::IsNull("deleted_at".into());
2187 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2188 assert!(sql.contains("deleted_at"));
2189 assert!(sql.contains("IS NULL"));
2190 assert!(params.is_empty());
2191 }
2192
2193 #[test]
2196 fn test_and_builder_basic() {
2197 let filter = Filter::and_builder(3)
2198 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2199 .push(Filter::Gt("score".into(), FilterValue::Int(100)))
2200 .push(Filter::IsNotNull("email".into()))
2201 .build();
2202
2203 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2204 assert!(sql.contains("AND"));
2205 assert_eq!(params.len(), 2); }
2207
2208 #[test]
2209 fn test_and_builder_empty() {
2210 let filter = Filter::and_builder(0).build();
2211 assert!(filter.is_none());
2212 }
2213
2214 #[test]
2215 fn test_and_builder_single() {
2216 let filter = Filter::and_builder(1)
2217 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2218 .build();
2219
2220 assert!(matches!(filter, Filter::Equals(_, _)));
2222 }
2223
2224 #[test]
2225 fn test_and_builder_filters_none() {
2226 let filter = Filter::and_builder(3)
2227 .push(Filter::None)
2228 .push(Filter::Equals("id".into(), FilterValue::Int(1)))
2229 .push(Filter::None)
2230 .build();
2231
2232 assert!(matches!(filter, Filter::Equals(_, _)));
2234 }
2235
2236 #[test]
2237 fn test_and_builder_push_if() {
2238 let include_deleted = false;
2239 let filter = Filter::and_builder(2)
2240 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2241 .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
2242 .build();
2243
2244 assert!(matches!(filter, Filter::Equals(_, _)));
2246 }
2247
2248 #[test]
2249 fn test_or_builder_basic() {
2250 let filter = Filter::or_builder(2)
2251 .push(Filter::Equals(
2252 "role".into(),
2253 FilterValue::String("admin".into()),
2254 ))
2255 .push(Filter::Equals(
2256 "role".into(),
2257 FilterValue::String("moderator".into()),
2258 ))
2259 .build();
2260
2261 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2262 assert!(sql.contains("OR"));
2263 }
2264
2265 #[test]
2266 fn test_or_builder_empty() {
2267 let filter = Filter::or_builder(0).build();
2268 assert!(filter.is_none());
2269 }
2270
2271 #[test]
2272 fn test_or_builder_single() {
2273 let filter = Filter::or_builder(1)
2274 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2275 .build();
2276
2277 assert!(matches!(filter, Filter::Equals(_, _)));
2279 }
2280
2281 #[test]
2282 fn test_fluent_builder_and() {
2283 let filter = Filter::builder()
2284 .eq("status", "active")
2285 .gt("age", 18)
2286 .is_not_null("email")
2287 .build_and();
2288
2289 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2290 assert!(sql.contains("AND"));
2291 assert_eq!(params.len(), 2);
2292 }
2293
2294 #[test]
2295 fn test_fluent_builder_or() {
2296 let filter = Filter::builder()
2297 .eq("role", "admin")
2298 .eq("role", "moderator")
2299 .build_or();
2300
2301 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2302 assert!(sql.contains("OR"));
2303 }
2304
2305 #[test]
2306 fn test_fluent_builder_with_capacity() {
2307 let filter = Filter::builder()
2308 .with_capacity(5)
2309 .eq("a", 1)
2310 .ne("b", 2)
2311 .lt("c", 3)
2312 .lte("d", 4)
2313 .gte("e", 5)
2314 .build_and();
2315
2316 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2317 assert!(sql.contains("AND"));
2318 assert_eq!(params.len(), 5);
2319 }
2320
2321 #[test]
2322 fn test_fluent_builder_string_operations() {
2323 let filter = Filter::builder()
2324 .contains("name", "john")
2325 .starts_with("email", "admin")
2326 .ends_with("domain", ".com")
2327 .build_and();
2328
2329 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2330 assert!(sql.contains("LIKE"));
2331 }
2332
2333 #[test]
2334 fn test_fluent_builder_null_operations() {
2335 let filter = Filter::builder()
2336 .is_null("deleted_at")
2337 .is_not_null("created_at")
2338 .build_and();
2339
2340 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2341 assert!(sql.contains("IS NULL"));
2342 assert!(sql.contains("IS NOT NULL"));
2343 }
2344
2345 #[test]
2346 fn test_fluent_builder_in_operations() {
2347 let filter = Filter::builder()
2348 .is_in("status", vec!["pending", "processing"])
2349 .not_in("role", vec!["banned", "suspended"])
2350 .build_and();
2351
2352 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2353 assert!(sql.contains("IN"));
2354 assert!(sql.contains("NOT IN"));
2355 }
2356
2357 #[test]
2358 fn test_fluent_builder_filter_if() {
2359 let include_archived = false;
2360 let filter = Filter::builder()
2361 .eq("active", true)
2362 .filter_if(
2363 include_archived,
2364 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2365 )
2366 .build_and();
2367
2368 assert!(matches!(filter, Filter::Equals(_, _)));
2370 }
2371
2372 #[test]
2373 fn test_fluent_builder_filter_if_some() {
2374 let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2375 let filter = Filter::builder()
2376 .eq("id", 1)
2377 .filter_if_some(maybe_status)
2378 .build_and();
2379
2380 assert!(matches!(filter, Filter::And(_)));
2381 }
2382
2383 #[test]
2384 fn test_and_builder_extend() {
2385 let extra_filters = vec![
2386 Filter::Gt("score".into(), FilterValue::Int(100)),
2387 Filter::Lt("score".into(), FilterValue::Int(1000)),
2388 ];
2389
2390 let filter = Filter::and_builder(3)
2391 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2392 .extend(extra_filters)
2393 .build();
2394
2395 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2396 assert!(sql.contains("AND"));
2397 assert_eq!(params.len(), 3);
2398 }
2399
2400 #[test]
2401 fn test_builder_len_and_is_empty() {
2402 let mut builder = AndFilterBuilder::new();
2403 assert!(builder.is_empty());
2404 assert_eq!(builder.len(), 0);
2405
2406 builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2407 assert!(!builder.is_empty());
2408 assert_eq!(builder.len(), 1);
2409 }
2410
2411 #[test]
2414 fn test_and2_both_valid() {
2415 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2416 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2417 let filter = Filter::and2(a, b);
2418
2419 assert!(matches!(filter, Filter::And(_)));
2420 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2421 assert!(sql.contains("AND"));
2422 assert_eq!(params.len(), 2);
2423 }
2424
2425 #[test]
2426 fn test_and2_first_none() {
2427 let a = Filter::None;
2428 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2429 let filter = Filter::and2(a, b.clone());
2430
2431 assert_eq!(filter, b);
2432 }
2433
2434 #[test]
2435 fn test_and2_second_none() {
2436 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2437 let b = Filter::None;
2438 let filter = Filter::and2(a.clone(), b);
2439
2440 assert_eq!(filter, a);
2441 }
2442
2443 #[test]
2444 fn test_and2_both_none() {
2445 let filter = Filter::and2(Filter::None, Filter::None);
2446 assert!(filter.is_none());
2447 }
2448
2449 #[test]
2450 fn test_or2_both_valid() {
2451 let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2452 let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2453 let filter = Filter::or2(a, b);
2454
2455 assert!(matches!(filter, Filter::Or(_)));
2456 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2457 assert!(sql.contains("OR"));
2458 }
2459
2460 #[test]
2461 fn test_or2_first_none() {
2462 let a = Filter::None;
2463 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2464 let filter = Filter::or2(a, b.clone());
2465
2466 assert_eq!(filter, b);
2467 }
2468
2469 #[test]
2470 fn test_or2_second_none() {
2471 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2472 let b = Filter::None;
2473 let filter = Filter::or2(a.clone(), b);
2474
2475 assert_eq!(filter, a);
2476 }
2477
2478 #[test]
2479 fn test_or2_both_none() {
2480 let filter = Filter::or2(Filter::None, Filter::None);
2481 assert!(filter.is_none());
2482 }
2483
2484 #[test]
2487 fn to_sql_quotes_column_names_against_injection() {
2488 use crate::dialect::{Mssql, Mysql, Postgres};
2489
2490 let filter = Filter::Equals(r#"id" OR 1=1--"#.into(), FilterValue::Int(1));
2492
2493 let (sql_pg, _) = filter.to_sql(0, &Postgres);
2494 assert!(
2495 sql_pg.starts_with(r#""id"" OR 1=1--" ="#),
2496 "postgres did not quote col; got: {sql_pg}"
2497 );
2498
2499 let (sql_my, _) = filter.to_sql(0, &Mysql);
2500 assert!(
2501 sql_my.starts_with(r#"`id" OR 1=1--` ="#),
2502 "mysql did not quote col; got: {sql_my}"
2503 );
2504
2505 let (sql_ms, _) = filter.to_sql(0, &Mssql);
2506 assert!(
2507 sql_ms.starts_with(r#"[id" OR 1=1--] ="#),
2508 "mssql did not quote col; got: {sql_ms}"
2509 );
2510 }
2511
2512 #[test]
2513 fn to_sql_quotes_in_list_column_names() {
2514 use crate::dialect::Postgres;
2515 let filter = Filter::In("id".into(), vec![FilterValue::Int(1), FilterValue::Int(2)]);
2516 let (sql, _) = filter.to_sql(0, &Postgres);
2517 assert!(
2518 sql.starts_with(r#""id" IN ("#),
2519 "expected quoted id on IN, got: {sql}"
2520 );
2521 }
2522
2523 #[test]
2524 fn to_sql_emits_sequential_placeholders() {
2525 use crate::dialect::Postgres;
2530
2531 let f = Filter::In(
2533 "id".into(),
2534 vec![
2535 FilterValue::Int(1),
2536 FilterValue::Int(2),
2537 FilterValue::Int(3),
2538 ],
2539 );
2540 let (sql, params) = f.to_sql(0, &Postgres);
2541 assert_eq!(sql, r#""id" IN ($1, $2, $3)"#, "IN placeholders: {sql}");
2542 assert_eq!(params.len(), 3);
2543
2544 let f = Filter::And(Box::new([
2546 Filter::Equals("a".into(), FilterValue::Int(10)),
2547 Filter::Equals("b".into(), FilterValue::Int(20)),
2548 ]));
2549 let (sql, params) = f.to_sql(0, &Postgres);
2550 assert_eq!(sql, r#"("a" = $1 AND "b" = $2)"#, "AND placeholders: {sql}");
2551 assert_eq!(params.len(), 2);
2552
2553 let f = Filter::And(Box::new([
2555 Filter::Equals("a".into(), FilterValue::Int(1)),
2556 Filter::In("b".into(), vec![FilterValue::Int(2), FilterValue::Int(3)]),
2557 ]));
2558 let (sql, params) = f.to_sql(0, &Postgres);
2559 assert_eq!(
2560 sql, r#"("a" = $1 AND "b" IN ($2, $3))"#,
2561 "mixed placeholders: {sql}"
2562 );
2563 assert_eq!(params.len(), 3);
2564
2565 let f = Filter::Equals("a".into(), FilterValue::Int(7));
2568 let (sql, _) = f.to_sql(5, &Postgres);
2569 assert_eq!(sql, r#""a" = $6"#, "offset placeholder: {sql}");
2570
2571 let f = Filter::NotIn(
2573 "status".into(),
2574 vec![
2575 FilterValue::String("deleted".into()),
2576 FilterValue::String("archived".into()),
2577 ],
2578 );
2579 let (sql, params) = f.to_sql(0, &Postgres);
2580 assert_eq!(
2581 sql, r#""status" NOT IN ($1, $2)"#,
2582 "NotIn placeholders: {sql}"
2583 );
2584 assert_eq!(params.len(), 2);
2585
2586 let f = Filter::Or(Box::new([
2588 Filter::Equals("a".into(), FilterValue::Int(100)),
2589 Filter::Equals("b".into(), FilterValue::Int(200)),
2590 ]));
2591 let (sql, params) = f.to_sql(0, &Postgres);
2592 assert_eq!(sql, r#"("a" = $1 OR "b" = $2)"#, "OR placeholders: {sql}");
2593 assert_eq!(params.len(), 2);
2594
2595 let f = Filter::StartsWith("name".into(), "admin".into());
2597 let (sql, params) = f.to_sql(0, &Postgres);
2598 assert_eq!(sql, r#""name" LIKE $1"#, "StartsWith placeholder: {sql}");
2599 assert_eq!(params.len(), 1);
2600
2601 let f = Filter::And(Box::new([
2604 Filter::And(Box::new([
2605 Filter::Equals("a".into(), FilterValue::Int(1)),
2606 Filter::Equals("b".into(), FilterValue::Int(2)),
2607 ])),
2608 Filter::Equals("c".into(), FilterValue::Int(3)),
2609 ]));
2610 let (sql, params) = f.to_sql(0, &Postgres);
2611 assert_eq!(
2612 sql, r#"(("a" = $1 AND "b" = $2) AND "c" = $3)"#,
2613 "nested AND placeholders: {sql}"
2614 );
2615 assert_eq!(params.len(), 3);
2616
2617 let f = Filter::And(Box::new([
2619 Filter::Equals("a".into(), FilterValue::Int(10)),
2620 Filter::Or(Box::new([
2621 Filter::Equals("b".into(), FilterValue::Int(20)),
2622 Filter::Equals("c".into(), FilterValue::Int(30)),
2623 ])),
2624 ]));
2625 let (sql, params) = f.to_sql(0, &Postgres);
2626 assert_eq!(
2627 sql, r#"("a" = $1 AND ("b" = $2 OR "c" = $3))"#,
2628 "And-Or nesting: {sql}"
2629 );
2630 assert_eq!(params.len(), 3);
2631
2632 let f = Filter::In(
2634 "id".into(),
2635 vec![
2636 FilterValue::Int(1),
2637 FilterValue::Int(2),
2638 FilterValue::Int(3),
2639 ],
2640 );
2641 let (sql, params) = f.to_sql(5, &Postgres);
2642 assert_eq!(sql, r#""id" IN ($6, $7, $8)"#, "IN offset: {sql}");
2643 assert_eq!(params.len(), 3);
2644 }
2645
2646 #[test]
2647 fn to_sql_placeholders_are_dialect_specific() {
2648 use crate::dialect::{Mysql, Sqlite};
2651
2652 let in_filter = || {
2653 Filter::In(
2654 "id".into(),
2655 vec![
2656 FilterValue::Int(1),
2657 FilterValue::Int(2),
2658 FilterValue::Int(3),
2659 ],
2660 )
2661 };
2662
2663 let (sql, params) = in_filter().to_sql(0, &Sqlite);
2665 assert_eq!(sql, r#""id" IN (?1, ?2, ?3)"#, "SQLite IN: {sql}");
2666 assert_eq!(params.len(), 3);
2667
2668 let (sql, params) = in_filter().to_sql(0, &Mysql);
2670 assert_eq!(sql, "`id` IN (?, ?, ?)", "MySQL IN: {sql}");
2671 assert_eq!(params.len(), 3);
2672
2673 let f = Filter::And(Box::new([
2675 Filter::Equals("a".into(), FilterValue::Int(10)),
2676 Filter::Equals("b".into(), FilterValue::Int(20)),
2677 ]));
2678 let (sql, _) = f.to_sql(0, &Sqlite);
2679 assert_eq!(sql, r#"("a" = ?1 AND "b" = ?2)"#, "SQLite AND: {sql}");
2680 }
2681
2682 #[test]
2683 fn to_sql_quotes_null_checks() {
2684 use crate::dialect::Postgres;
2685 let filter = Filter::IsNull("deleted_at".into());
2686 let (sql, _) = filter.to_sql(0, &Postgres);
2687 assert_eq!(sql, r#""deleted_at" IS NULL"#);
2688 }
2689
2690 #[test]
2691 fn to_sql_quotes_comparison_operators() {
2692 use crate::dialect::Postgres;
2693
2694 let filter = Filter::Lt("age".into(), FilterValue::Int(18));
2695 let (sql, _) = filter.to_sql(0, &Postgres);
2696 assert!(sql.starts_with(r#""age" < "#), "Lt not quoted: {sql}");
2697
2698 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
2699 let (sql, _) = filter.to_sql(0, &Postgres);
2700 assert!(sql.starts_with(r#""price" <= "#), "Lte not quoted: {sql}");
2701
2702 let filter = Filter::Gt("score".into(), FilterValue::Int(0));
2703 let (sql, _) = filter.to_sql(0, &Postgres);
2704 assert!(sql.starts_with(r#""score" > "#), "Gt not quoted: {sql}");
2705
2706 let filter = Filter::Gte("quantity".into(), FilterValue::Int(1));
2707 let (sql, _) = filter.to_sql(0, &Postgres);
2708 assert!(
2709 sql.starts_with(r#""quantity" >= "#),
2710 "Gte not quoted: {sql}"
2711 );
2712
2713 let filter = Filter::NotEquals("status".into(), "deleted".into());
2714 let (sql, _) = filter.to_sql(0, &Postgres);
2715 assert!(
2716 sql.starts_with(r#""status" != "#),
2717 "NotEquals not quoted: {sql}"
2718 );
2719 }
2720
2721 #[test]
2722 fn to_sql_quotes_like_operators() {
2723 use crate::dialect::Postgres;
2724
2725 let filter = Filter::Contains("email".into(), "example".into());
2726 let (sql, _) = filter.to_sql(0, &Postgres);
2727 assert!(
2728 sql.starts_with(r#""email" LIKE "#),
2729 "Contains not quoted: {sql}"
2730 );
2731
2732 let filter = Filter::StartsWith("name".into(), "admin".into());
2733 let (sql, _) = filter.to_sql(0, &Postgres);
2734 assert!(
2735 sql.starts_with(r#""name" LIKE "#),
2736 "StartsWith not quoted: {sql}"
2737 );
2738
2739 let filter = Filter::EndsWith("domain".into(), ".com".into());
2740 let (sql, _) = filter.to_sql(0, &Postgres);
2741 assert!(
2742 sql.starts_with(r#""domain" LIKE "#),
2743 "EndsWith not quoted: {sql}"
2744 );
2745 }
2746
2747 #[test]
2748 fn to_sql_quotes_not_in() {
2749 use crate::dialect::Postgres;
2750 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
2751 let (sql, _) = filter.to_sql(0, &Postgres);
2752 assert!(
2753 sql.starts_with(r#""status" NOT IN ("#),
2754 "NotIn not quoted: {sql}"
2755 );
2756 }
2757
2758 #[test]
2759 fn to_sql_quotes_is_not_null() {
2760 use crate::dialect::Postgres;
2761 let filter = Filter::IsNotNull("verified_at".into());
2762 let (sql, _) = filter.to_sql(0, &Postgres);
2763 assert_eq!(sql, r#""verified_at" IS NOT NULL"#);
2764 }
2765
2766 #[test]
2767 fn filter_value_from_u64_in_range() {
2768 assert_eq!(FilterValue::from(42u64), FilterValue::Int(42));
2769 assert_eq!(FilterValue::from(0u64), FilterValue::Int(0));
2770 let max_safe = i64::MAX as u64;
2771 assert_eq!(FilterValue::from(max_safe), FilterValue::Int(i64::MAX));
2772 }
2773
2774 #[test]
2775 #[should_panic(expected = "u64 value exceeds i64::MAX")]
2776 fn filter_value_from_u64_overflow_panics() {
2777 let _ = FilterValue::from(u64::MAX);
2778 }
2779
2780 #[test]
2781 fn filter_value_from_chrono_datetime_utc_rfc3339() {
2782 use chrono::{TimeZone, Utc};
2783 let dt = Utc.with_ymd_and_hms(2020, 1, 15, 10, 30, 45).unwrap();
2784 let fv = FilterValue::from(dt);
2785 assert_eq!(
2786 fv,
2787 FilterValue::String("2020-01-15T10:30:45.000000Z".to_string())
2788 );
2789 }
2790
2791 #[test]
2792 fn filter_value_from_chrono_naive_datetime_iso() {
2793 use chrono::NaiveDate;
2794 let dt = NaiveDate::from_ymd_opt(2020, 1, 15)
2795 .unwrap()
2796 .and_hms_opt(10, 30, 45)
2797 .unwrap();
2798 let fv = FilterValue::from(dt);
2799 assert_eq!(
2800 fv,
2801 FilterValue::String("2020-01-15T10:30:45.000000".to_string())
2802 );
2803 }
2804
2805 #[test]
2806 fn filter_value_from_chrono_naive_date() {
2807 use chrono::NaiveDate;
2808 let d = NaiveDate::from_ymd_opt(2020, 1, 15).unwrap();
2809 assert_eq!(
2810 FilterValue::from(d),
2811 FilterValue::String("2020-01-15".to_string())
2812 );
2813 }
2814
2815 #[test]
2816 fn filter_value_from_chrono_naive_time() {
2817 use chrono::NaiveTime;
2818 let t = NaiveTime::from_hms_opt(10, 30, 45).unwrap();
2819 assert_eq!(
2820 FilterValue::from(t),
2821 FilterValue::String("10:30:45.000000".to_string())
2822 );
2823 }
2824
2825 #[test]
2832 fn filter_value_from_uuid_is_lowercase_hyphenated() {
2833 use uuid::Uuid;
2838 let u = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
2839 match FilterValue::from(u) {
2840 FilterValue::String(ref s) => {
2841 assert_eq!(s, "550e8400-e29b-41d4-a716-446655440000");
2842 assert_eq!(s, &u.to_string());
2843 }
2844 other => panic!("expected FilterValue::String, got {other:?}"),
2845 }
2846 }
2847
2848 #[test]
2849 fn filter_value_from_uuid_nil_round_trips() {
2850 use uuid::Uuid;
2851 let u = Uuid::nil();
2852 assert_eq!(
2853 FilterValue::from(u),
2854 FilterValue::String("00000000-0000-0000-0000-000000000000".to_string())
2855 );
2856 }
2857
2858 #[test]
2859 fn filter_value_from_decimal_uses_to_string_not_f64() {
2860 use rust_decimal::Decimal;
2864 use std::str::FromStr;
2865 let d = Decimal::from_str("3.14").unwrap();
2866 assert_eq!(
2867 FilterValue::from(d),
2868 FilterValue::String("3.14".to_string())
2869 );
2870 }
2871
2872 #[test]
2873 fn filter_value_from_decimal_high_precision_preserved() {
2874 use rust_decimal::Decimal;
2875 use std::str::FromStr;
2876 let d = Decimal::from_str("1234567890.1234567890").unwrap();
2878 match FilterValue::from(d) {
2879 FilterValue::String(ref s) => {
2880 assert_eq!(s, "1234567890.1234567890");
2881 }
2882 other => panic!("expected FilterValue::String, got {other:?}"),
2883 }
2884 }
2885
2886 #[test]
2887 fn filter_value_from_serde_json_value_keeps_json_variant() {
2888 let v = serde_json::json!({"key": "value", "nested": [1, 2, 3]});
2889 match FilterValue::from(v.clone()) {
2890 FilterValue::Json(inner) => {
2891 assert_eq!(inner, v);
2892 }
2893 other => panic!("expected FilterValue::Json, got {other:?}"),
2894 }
2895 }
2896
2897 #[test]
2898 fn filter_value_from_serde_json_null_keeps_json_variant() {
2899 let v = serde_json::Value::Null;
2904 match FilterValue::from(v) {
2905 FilterValue::Json(serde_json::Value::Null) => {}
2906 other => panic!("expected FilterValue::Json(Null), got {other:?}"),
2907 }
2908 }
2909
2910 #[test]
2911 fn filter_value_from_option_none_maps_to_null() {
2912 let none_i32: Option<i32> = None;
2915 assert_eq!(FilterValue::from(none_i32), FilterValue::Null);
2916 let none_string: Option<String> = None;
2917 assert_eq!(FilterValue::from(none_string), FilterValue::Null);
2918 }
2919
2920 #[test]
2921 fn filter_value_from_signed_integer_extremes() {
2922 assert_eq!(FilterValue::from(i8::MIN), FilterValue::Int(i8::MIN as i64));
2925 assert_eq!(FilterValue::from(i8::MAX), FilterValue::Int(i8::MAX as i64));
2926 assert_eq!(
2927 FilterValue::from(i16::MIN),
2928 FilterValue::Int(i16::MIN as i64)
2929 );
2930 assert_eq!(
2931 FilterValue::from(i16::MAX),
2932 FilterValue::Int(i16::MAX as i64)
2933 );
2934 }
2935
2936 #[test]
2937 fn filter_value_from_unsigned_integer_extremes() {
2938 assert_eq!(FilterValue::from(u8::MAX), FilterValue::Int(u8::MAX as i64));
2941 assert_eq!(
2942 FilterValue::from(u16::MAX),
2943 FilterValue::Int(u16::MAX as i64)
2944 );
2945 assert_eq!(
2946 FilterValue::from(u32::MAX),
2947 FilterValue::Int(u32::MAX as i64)
2948 );
2949 assert_eq!(FilterValue::from(u32::MAX), FilterValue::Int(4_294_967_295));
2951 }
2952
2953 #[test]
2954 fn filter_value_from_f32_widens_to_f64() {
2955 let v: f32 = 1.5;
2960 assert_eq!(FilterValue::from(v), FilterValue::Float(1.5));
2961 }
2962
2963 #[test]
2970 fn to_filter_value_option_some_some() {
2971 let v: Option<i32> = Some(42);
2972 assert_eq!(v.to_filter_value(), FilterValue::Int(42));
2973 }
2974
2975 #[test]
2976 fn to_filter_value_option_none_is_null() {
2977 let v: Option<i32> = None;
2978 assert_eq!(v.to_filter_value(), FilterValue::Null);
2979 }
2980
2981 #[test]
2982 fn to_filter_value_uuid_is_string() {
2983 let id = uuid::Uuid::nil();
2984 assert_eq!(id.to_filter_value(), FilterValue::String(id.to_string()));
2985 }
2986
2987 #[test]
2988 fn to_filter_value_bool_is_bool() {
2989 assert_eq!(true.to_filter_value(), FilterValue::Bool(true));
2990 }
2991
2992 #[test]
2993 fn scalar_subquery_lowers_to_inline_sql_with_dialect_placeholders() {
2994 use crate::dialect::Postgres;
2995 let f = Filter::ScalarSubquery {
2996 sql: Cow::Borrowed(
2997 "(SELECT COUNT(*) FROM posts p WHERE p.author_id = users.id AND p.published = {0}) > {1}",
2998 ),
2999 params: vec![FilterValue::Bool(true), FilterValue::Int(5)],
3000 };
3001 let (sql, params) = f.to_sql(0, &Postgres);
3002 assert_eq!(
3003 sql,
3004 "((SELECT COUNT(*) FROM posts p WHERE p.author_id = users.id AND p.published = $1) > $2)"
3005 );
3006 assert_eq!(params, vec![FilterValue::Bool(true), FilterValue::Int(5)]);
3007 }
3008
3009 #[test]
3010 fn scalar_subquery_offsets_placeholders_inside_and() {
3011 use crate::dialect::Postgres;
3012 let f = Filter::and([
3013 Filter::Equals("active".into(), FilterValue::Bool(true)),
3014 Filter::ScalarSubquery {
3015 sql: Cow::Borrowed(
3016 "(SELECT COUNT(*) FROM posts p WHERE p.author_id = users.id) >= {0}",
3017 ),
3018 params: vec![FilterValue::Int(1)],
3019 },
3020 ]);
3021 let (sql, params) = f.to_sql(0, &Postgres);
3022 assert!(sql.contains("$1"));
3024 assert!(sql.contains("$2"));
3025 assert_eq!(params.len(), 2);
3026 assert_eq!(params[0], FilterValue::Bool(true));
3027 assert_eq!(params[1], FilterValue::Int(1));
3028 }
3029
3030 #[test]
3031 fn scalar_subquery_handles_out_of_order_and_repeated_placeholders() {
3032 use crate::dialect::Postgres;
3033 let f = Filter::ScalarSubquery {
3034 sql: Cow::Borrowed("{1} = {0} AND {1} > {0}"),
3035 params: vec![FilterValue::Int(1), FilterValue::Int(2)],
3036 };
3037 let (sql, params) = f.to_sql(0, &Postgres);
3038 assert_eq!(sql, "($2 = $1 AND $2 > $1)");
3041 assert_eq!(params, vec![FilterValue::Int(1), FilterValue::Int(2)]);
3042 }
3043}