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
624fn escape_like_value(s: &str) -> String {
632 s.replace('\\', "\\\\")
633 .replace('%', "\\%")
634 .replace('_', "\\_")
635}
636
637impl Filter {
638 #[inline(always)]
640 pub fn none() -> Self {
641 Self::None
642 }
643
644 #[inline(always)]
646 pub fn is_none(&self) -> bool {
647 matches!(self, Self::None)
648 }
649
650 #[inline]
656 pub fn and(filters: impl IntoIterator<Item = Filter>) -> Self {
657 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
658 let count = filters.len();
659 let result = match count {
660 0 => Self::None,
661 1 => filters.into_iter().next().unwrap(),
662 _ => Self::And(filters.into_boxed_slice()),
663 };
664 debug!(count, "Filter::and() created");
665 result
666 }
667
668 #[inline(always)]
672 pub fn and2(a: Filter, b: Filter) -> Self {
673 match (a.is_none(), b.is_none()) {
674 (true, true) => Self::None,
675 (true, false) => b,
676 (false, true) => a,
677 (false, false) => Self::And(Box::new([a, b])),
678 }
679 }
680
681 #[inline]
687 pub fn or(filters: impl IntoIterator<Item = Filter>) -> Self {
688 let filters: Vec<_> = filters.into_iter().filter(|f| !f.is_none()).collect();
689 let count = filters.len();
690 let result = match count {
691 0 => Self::None,
692 1 => filters.into_iter().next().unwrap(),
693 _ => Self::Or(filters.into_boxed_slice()),
694 };
695 debug!(count, "Filter::or() created");
696 result
697 }
698
699 #[inline(always)]
703 pub fn or2(a: Filter, b: Filter) -> Self {
704 match (a.is_none(), b.is_none()) {
705 (true, true) => Self::None,
706 (true, false) => b,
707 (false, true) => a,
708 (false, false) => Self::Or(Box::new([a, b])),
709 }
710 }
711
712 #[inline(always)]
733 pub fn and_n<const N: usize>(filters: [Filter; N]) -> Self {
734 Self::And(Box::new(filters))
736 }
737
738 #[inline(always)]
743 pub fn or_n<const N: usize>(filters: [Filter; N]) -> Self {
744 Self::Or(Box::new(filters))
745 }
746
747 #[inline(always)]
749 pub fn and3(a: Filter, b: Filter, c: Filter) -> Self {
750 Self::And(Box::new([a, b, c]))
751 }
752
753 #[inline(always)]
755 pub fn and4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
756 Self::And(Box::new([a, b, c, d]))
757 }
758
759 #[inline(always)]
761 pub fn and5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
762 Self::And(Box::new([a, b, c, d, e]))
763 }
764
765 #[inline(always)]
767 pub fn or3(a: Filter, b: Filter, c: Filter) -> Self {
768 Self::Or(Box::new([a, b, c]))
769 }
770
771 #[inline(always)]
773 pub fn or4(a: Filter, b: Filter, c: Filter, d: Filter) -> Self {
774 Self::Or(Box::new([a, b, c, d]))
775 }
776
777 #[inline(always)]
779 pub fn or5(a: Filter, b: Filter, c: Filter, d: Filter, e: Filter) -> Self {
780 Self::Or(Box::new([a, b, c, d, e]))
781 }
782
783 #[inline]
792 pub fn in_i64(field: impl Into<FieldName>, values: impl IntoIterator<Item = i64>) -> Self {
793 let list: ValueList = values.into_iter().map(FilterValue::Int).collect();
794 Self::In(field.into(), list)
795 }
796
797 #[inline]
799 pub fn in_i32(field: impl Into<FieldName>, values: impl IntoIterator<Item = i32>) -> Self {
800 let list: ValueList = values
801 .into_iter()
802 .map(|v| FilterValue::Int(v as i64))
803 .collect();
804 Self::In(field.into(), list)
805 }
806
807 #[inline]
809 pub fn in_strings(
810 field: impl Into<FieldName>,
811 values: impl IntoIterator<Item = String>,
812 ) -> Self {
813 let list: ValueList = values.into_iter().map(FilterValue::String).collect();
814 Self::In(field.into(), list)
815 }
816
817 #[inline]
821 pub fn in_values(field: impl Into<FieldName>, values: ValueList) -> Self {
822 Self::In(field.into(), values)
823 }
824
825 #[inline]
829 pub fn in_range(field: impl Into<FieldName>, range: std::ops::Range<i64>) -> Self {
830 let list: ValueList = range.map(FilterValue::Int).collect();
831 Self::In(field.into(), list)
832 }
833
834 #[inline(always)]
839 pub fn in_i64_slice(field: impl Into<FieldName>, values: &[i64]) -> Self {
840 let mut list = Vec::with_capacity(values.len());
841 for &v in values {
842 list.push(FilterValue::Int(v));
843 }
844 Self::In(field.into(), list)
845 }
846
847 #[inline(always)]
849 pub fn in_i32_slice(field: impl Into<FieldName>, values: &[i32]) -> Self {
850 let mut list = Vec::with_capacity(values.len());
851 for &v in values {
852 list.push(FilterValue::Int(v as i64));
853 }
854 Self::In(field.into(), list)
855 }
856
857 #[inline(always)]
859 pub fn in_str_slice(field: impl Into<FieldName>, values: &[&str]) -> Self {
860 let mut list = Vec::with_capacity(values.len());
861 for &v in values {
862 list.push(FilterValue::String(v.to_string()));
863 }
864 Self::In(field.into(), list)
865 }
866
867 #[inline]
869 #[allow(clippy::should_implement_trait)]
870 pub fn not(filter: Filter) -> Self {
871 if filter.is_none() {
872 return Self::None;
873 }
874 Self::Not(Box::new(filter))
875 }
876
877 #[inline]
891 pub fn in_slice<T: Into<FilterValue> + Clone>(
892 field: impl Into<FieldName>,
893 values: &[T],
894 ) -> Self {
895 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
896 Self::In(field.into(), list)
897 }
898
899 #[inline]
910 pub fn not_in_slice<T: Into<FilterValue> + Clone>(
911 field: impl Into<FieldName>,
912 values: &[T],
913 ) -> Self {
914 let list: ValueList = values.iter().map(|v| v.clone().into()).collect();
915 Self::NotIn(field.into(), list)
916 }
917
918 #[inline]
930 pub fn in_array<T: Into<FilterValue>, const N: usize>(
931 field: impl Into<FieldName>,
932 values: [T; N],
933 ) -> Self {
934 let list: ValueList = values.into_iter().map(Into::into).collect();
935 Self::In(field.into(), list)
936 }
937
938 #[inline]
940 pub fn not_in_array<T: Into<FilterValue>, const N: usize>(
941 field: impl Into<FieldName>,
942 values: [T; N],
943 ) -> Self {
944 let list: ValueList = values.into_iter().map(Into::into).collect();
945 Self::NotIn(field.into(), list)
946 }
947
948 pub fn and_then(self, other: Filter) -> Self {
950 if self.is_none() {
951 return other;
952 }
953 if other.is_none() {
954 return self;
955 }
956 match self {
957 Self::And(filters) => {
958 let mut vec: Vec<_> = filters.into_vec();
960 vec.push(other);
961 Self::And(vec.into_boxed_slice())
962 }
963 _ => Self::And(Box::new([self, other])),
964 }
965 }
966
967 pub fn or_else(self, other: Filter) -> Self {
969 if self.is_none() {
970 return other;
971 }
972 if other.is_none() {
973 return self;
974 }
975 match self {
976 Self::Or(filters) => {
977 let mut vec: Vec<_> = filters.into_vec();
979 vec.push(other);
980 Self::Or(vec.into_boxed_slice())
981 }
982 _ => Self::Or(Box::new([self, other])),
983 }
984 }
985
986 pub fn to_sql(
989 &self,
990 param_offset: usize,
991 dialect: &dyn crate::dialect::SqlDialect,
992 ) -> (String, Vec<FilterValue>) {
993 let mut params = Vec::new();
994 let sql = self.to_sql_with_params(param_offset, &mut params, dialect);
995 (sql, params)
996 }
997
998 fn to_sql_with_params(
1019 &self,
1020 param_idx: usize,
1021 params: &mut Vec<FilterValue>,
1022 dialect: &dyn crate::dialect::SqlDialect,
1023 ) -> String {
1024 match self {
1025 Self::None => "TRUE".to_string(),
1026
1027 Self::Equals(col, val) => {
1028 let c = dialect.quote_ident(col);
1029 if val.is_null() {
1030 format!("{} IS NULL", c)
1031 } else {
1032 params.push(val.clone());
1033 format!("{} = {}", c, dialect.placeholder(param_idx + 1))
1034 }
1035 }
1036 Self::NotEquals(col, val) => {
1037 let c = dialect.quote_ident(col);
1038 if val.is_null() {
1039 format!("{} IS NOT NULL", c)
1040 } else {
1041 params.push(val.clone());
1042 format!("{} != {}", c, dialect.placeholder(param_idx + 1))
1043 }
1044 }
1045
1046 Self::Lt(col, val) => {
1047 let c = dialect.quote_ident(col);
1048 params.push(val.clone());
1049 format!("{} < {}", c, dialect.placeholder(param_idx + 1))
1050 }
1051 Self::Lte(col, val) => {
1052 let c = dialect.quote_ident(col);
1053 params.push(val.clone());
1054 format!("{} <= {}", c, dialect.placeholder(param_idx + 1))
1055 }
1056 Self::Gt(col, val) => {
1057 let c = dialect.quote_ident(col);
1058 params.push(val.clone());
1059 format!("{} > {}", c, dialect.placeholder(param_idx + 1))
1060 }
1061 Self::Gte(col, val) => {
1062 let c = dialect.quote_ident(col);
1063 params.push(val.clone());
1064 format!("{} >= {}", c, dialect.placeholder(param_idx + 1))
1065 }
1066
1067 Self::In(col, values) => {
1068 if values.is_empty() {
1069 return "FALSE".to_string();
1070 }
1071 let c = dialect.quote_ident(col);
1072 let placeholders: Vec<_> = values
1073 .iter()
1074 .enumerate()
1075 .map(|(i, v)| {
1076 params.push(v.clone());
1077 dialect.placeholder(param_idx + i + 1)
1078 })
1079 .collect();
1080 format!("{} IN ({})", c, placeholders.join(", "))
1081 }
1082 Self::NotIn(col, values) => {
1083 if values.is_empty() {
1084 return "TRUE".to_string();
1085 }
1086 let c = dialect.quote_ident(col);
1087 let placeholders: Vec<_> = values
1088 .iter()
1089 .enumerate()
1090 .map(|(i, v)| {
1091 params.push(v.clone());
1092 dialect.placeholder(param_idx + i + 1)
1093 })
1094 .collect();
1095 format!("{} NOT IN ({})", c, placeholders.join(", "))
1096 }
1097
1098 Self::Contains(col, val) => {
1099 let c = dialect.quote_ident(col);
1100 if let FilterValue::String(s) = val {
1101 params.push(FilterValue::String(format!("%{}%", escape_like_value(s))));
1102 } else {
1103 params.push(val.clone());
1104 }
1105 format!(
1106 "{} LIKE {}{}",
1107 c,
1108 dialect.placeholder(param_idx + 1),
1109 dialect.like_escape_clause()
1110 )
1111 }
1112 Self::StartsWith(col, val) => {
1113 let c = dialect.quote_ident(col);
1114 if let FilterValue::String(s) = val {
1115 params.push(FilterValue::String(format!("{}%", escape_like_value(s))));
1116 } else {
1117 params.push(val.clone());
1118 }
1119 format!(
1120 "{} LIKE {}{}",
1121 c,
1122 dialect.placeholder(param_idx + 1),
1123 dialect.like_escape_clause()
1124 )
1125 }
1126 Self::EndsWith(col, val) => {
1127 let c = dialect.quote_ident(col);
1128 if let FilterValue::String(s) = val {
1129 params.push(FilterValue::String(format!("%{}", escape_like_value(s))));
1130 } else {
1131 params.push(val.clone());
1132 }
1133 format!(
1134 "{} LIKE {}{}",
1135 c,
1136 dialect.placeholder(param_idx + 1),
1137 dialect.like_escape_clause()
1138 )
1139 }
1140
1141 Self::IsNull(col) => {
1142 let c = dialect.quote_ident(col);
1143 format!("{} IS NULL", c)
1144 }
1145 Self::IsNotNull(col) => {
1146 let c = dialect.quote_ident(col);
1147 format!("{} IS NOT NULL", c)
1148 }
1149
1150 Self::And(filters) => {
1151 if filters.is_empty() {
1152 return "TRUE".to_string();
1153 }
1154 let base = param_idx - params.len();
1159 let parts: Vec<_> = filters
1160 .iter()
1161 .map(|f| f.to_sql_with_params(base + params.len(), params, dialect))
1162 .collect();
1163 format!("({})", parts.join(" AND "))
1164 }
1165 Self::Or(filters) => {
1166 if filters.is_empty() {
1167 return "FALSE".to_string();
1168 }
1169 let base = param_idx - params.len();
1170 let parts: Vec<_> = filters
1171 .iter()
1172 .map(|f| f.to_sql_with_params(base + params.len(), params, dialect))
1173 .collect();
1174 format!("({})", parts.join(" OR "))
1175 }
1176 Self::Not(filter) => {
1177 let inner = filter.to_sql_with_params(param_idx, params, dialect);
1178 format!("NOT ({})", inner)
1179 }
1180
1181 Self::ScalarSubquery {
1182 sql,
1183 params: inner_params,
1184 } => {
1185 let base = param_idx;
1190 for v in inner_params.iter() {
1194 params.push(v.clone());
1195 }
1196 let mut out = String::with_capacity(sql.len() + inner_params.len() * 4);
1197 let mut chars = sql.chars().peekable();
1198 while let Some(ch) = chars.next() {
1199 if ch == '{' {
1200 let mut digits = String::new();
1202 while let Some(&c) = chars.peek() {
1203 if c == '}' {
1204 chars.next();
1205 break;
1206 }
1207 digits.push(c);
1208 chars.next();
1209 }
1210 let n: usize = digits.parse().unwrap_or_else(|_| {
1211 panic!(
1212 "Filter::ScalarSubquery: invalid placeholder index `{{{}}}`",
1213 digits
1214 )
1215 });
1216 if n >= inner_params.len() {
1217 panic!(
1218 "Filter::ScalarSubquery: placeholder {{{}}} out of range (have {} params)",
1219 n,
1220 inner_params.len()
1221 );
1222 }
1223 out.push_str(&dialect.placeholder(base + n + 1));
1224 } else {
1225 out.push(ch);
1226 }
1227 }
1228 format!("({})", out)
1229 }
1230 }
1231 }
1232
1233 #[inline]
1251 pub fn and_builder(capacity: usize) -> AndFilterBuilder {
1252 AndFilterBuilder::with_capacity(capacity)
1253 }
1254
1255 #[inline]
1272 pub fn or_builder(capacity: usize) -> OrFilterBuilder {
1273 OrFilterBuilder::with_capacity(capacity)
1274 }
1275
1276 #[inline]
1292 pub fn builder() -> FluentFilterBuilder {
1293 FluentFilterBuilder::new()
1294 }
1295}
1296
1297#[derive(Debug, Clone)]
1301pub struct AndFilterBuilder {
1302 filters: Vec<Filter>,
1303}
1304
1305impl AndFilterBuilder {
1306 #[inline]
1308 pub fn new() -> Self {
1309 Self {
1310 filters: Vec::new(),
1311 }
1312 }
1313
1314 #[inline]
1316 pub fn with_capacity(capacity: usize) -> Self {
1317 Self {
1318 filters: Vec::with_capacity(capacity),
1319 }
1320 }
1321
1322 #[inline]
1324 pub fn push(mut self, filter: Filter) -> Self {
1325 if !filter.is_none() {
1326 self.filters.push(filter);
1327 }
1328 self
1329 }
1330
1331 #[inline]
1333 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1334 self.filters
1335 .extend(filters.into_iter().filter(|f| !f.is_none()));
1336 self
1337 }
1338
1339 #[inline]
1341 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1342 if condition { self.push(filter) } else { self }
1343 }
1344
1345 #[inline]
1347 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1348 where
1349 F: Into<Filter>,
1350 {
1351 match opt {
1352 Some(f) => self.push(f.into()),
1353 None => self,
1354 }
1355 }
1356
1357 #[inline]
1359 pub fn build(self) -> Filter {
1360 match self.filters.len() {
1361 0 => Filter::None,
1362 1 => self.filters.into_iter().next().unwrap(),
1363 _ => Filter::And(self.filters.into_boxed_slice()),
1364 }
1365 }
1366
1367 #[inline]
1369 pub fn len(&self) -> usize {
1370 self.filters.len()
1371 }
1372
1373 #[inline]
1375 pub fn is_empty(&self) -> bool {
1376 self.filters.is_empty()
1377 }
1378}
1379
1380impl Default for AndFilterBuilder {
1381 fn default() -> Self {
1382 Self::new()
1383 }
1384}
1385
1386#[derive(Debug, Clone)]
1390pub struct OrFilterBuilder {
1391 filters: Vec<Filter>,
1392}
1393
1394impl OrFilterBuilder {
1395 #[inline]
1397 pub fn new() -> Self {
1398 Self {
1399 filters: Vec::new(),
1400 }
1401 }
1402
1403 #[inline]
1405 pub fn with_capacity(capacity: usize) -> Self {
1406 Self {
1407 filters: Vec::with_capacity(capacity),
1408 }
1409 }
1410
1411 #[inline]
1413 pub fn push(mut self, filter: Filter) -> Self {
1414 if !filter.is_none() {
1415 self.filters.push(filter);
1416 }
1417 self
1418 }
1419
1420 #[inline]
1422 pub fn extend(mut self, filters: impl IntoIterator<Item = Filter>) -> Self {
1423 self.filters
1424 .extend(filters.into_iter().filter(|f| !f.is_none()));
1425 self
1426 }
1427
1428 #[inline]
1430 pub fn push_if(self, condition: bool, filter: Filter) -> Self {
1431 if condition { self.push(filter) } else { self }
1432 }
1433
1434 #[inline]
1436 pub fn push_if_some<F>(self, opt: Option<F>) -> Self
1437 where
1438 F: Into<Filter>,
1439 {
1440 match opt {
1441 Some(f) => self.push(f.into()),
1442 None => self,
1443 }
1444 }
1445
1446 #[inline]
1448 pub fn build(self) -> Filter {
1449 match self.filters.len() {
1450 0 => Filter::None,
1451 1 => self.filters.into_iter().next().unwrap(),
1452 _ => Filter::Or(self.filters.into_boxed_slice()),
1453 }
1454 }
1455
1456 #[inline]
1458 pub fn len(&self) -> usize {
1459 self.filters.len()
1460 }
1461
1462 #[inline]
1464 pub fn is_empty(&self) -> bool {
1465 self.filters.is_empty()
1466 }
1467}
1468
1469impl Default for OrFilterBuilder {
1470 fn default() -> Self {
1471 Self::new()
1472 }
1473}
1474
1475#[derive(Debug, Clone)]
1500pub struct FluentFilterBuilder {
1501 filters: Vec<Filter>,
1502}
1503
1504impl FluentFilterBuilder {
1505 #[inline]
1507 pub fn new() -> Self {
1508 Self {
1509 filters: Vec::new(),
1510 }
1511 }
1512
1513 #[inline]
1515 pub fn with_capacity(mut self, capacity: usize) -> Self {
1516 self.filters.reserve(capacity);
1517 self
1518 }
1519
1520 #[inline]
1522 pub fn eq<F, V>(mut self, field: F, value: V) -> Self
1523 where
1524 F: Into<FieldName>,
1525 V: Into<FilterValue>,
1526 {
1527 self.filters
1528 .push(Filter::Equals(field.into(), value.into()));
1529 self
1530 }
1531
1532 #[inline]
1534 pub fn ne<F, V>(mut self, field: F, value: V) -> Self
1535 where
1536 F: Into<FieldName>,
1537 V: Into<FilterValue>,
1538 {
1539 self.filters
1540 .push(Filter::NotEquals(field.into(), value.into()));
1541 self
1542 }
1543
1544 #[inline]
1546 pub fn lt<F, V>(mut self, field: F, value: V) -> Self
1547 where
1548 F: Into<FieldName>,
1549 V: Into<FilterValue>,
1550 {
1551 self.filters.push(Filter::Lt(field.into(), value.into()));
1552 self
1553 }
1554
1555 #[inline]
1557 pub fn lte<F, V>(mut self, field: F, value: V) -> Self
1558 where
1559 F: Into<FieldName>,
1560 V: Into<FilterValue>,
1561 {
1562 self.filters.push(Filter::Lte(field.into(), value.into()));
1563 self
1564 }
1565
1566 #[inline]
1568 pub fn gt<F, V>(mut self, field: F, value: V) -> Self
1569 where
1570 F: Into<FieldName>,
1571 V: Into<FilterValue>,
1572 {
1573 self.filters.push(Filter::Gt(field.into(), value.into()));
1574 self
1575 }
1576
1577 #[inline]
1579 pub fn gte<F, V>(mut self, field: F, value: V) -> Self
1580 where
1581 F: Into<FieldName>,
1582 V: Into<FilterValue>,
1583 {
1584 self.filters.push(Filter::Gte(field.into(), value.into()));
1585 self
1586 }
1587
1588 #[inline]
1590 pub fn is_in<F, I, V>(mut self, field: F, values: I) -> Self
1591 where
1592 F: Into<FieldName>,
1593 I: IntoIterator<Item = V>,
1594 V: Into<FilterValue>,
1595 {
1596 self.filters.push(Filter::In(
1597 field.into(),
1598 values.into_iter().map(Into::into).collect(),
1599 ));
1600 self
1601 }
1602
1603 #[inline]
1605 pub fn not_in<F, I, V>(mut self, field: F, values: I) -> Self
1606 where
1607 F: Into<FieldName>,
1608 I: IntoIterator<Item = V>,
1609 V: Into<FilterValue>,
1610 {
1611 self.filters.push(Filter::NotIn(
1612 field.into(),
1613 values.into_iter().map(Into::into).collect(),
1614 ));
1615 self
1616 }
1617
1618 #[inline]
1620 pub fn contains<F, V>(mut self, field: F, value: V) -> Self
1621 where
1622 F: Into<FieldName>,
1623 V: Into<FilterValue>,
1624 {
1625 self.filters
1626 .push(Filter::Contains(field.into(), value.into()));
1627 self
1628 }
1629
1630 #[inline]
1632 pub fn starts_with<F, V>(mut self, field: F, value: V) -> Self
1633 where
1634 F: Into<FieldName>,
1635 V: Into<FilterValue>,
1636 {
1637 self.filters
1638 .push(Filter::StartsWith(field.into(), value.into()));
1639 self
1640 }
1641
1642 #[inline]
1644 pub fn ends_with<F, V>(mut self, field: F, value: V) -> Self
1645 where
1646 F: Into<FieldName>,
1647 V: Into<FilterValue>,
1648 {
1649 self.filters
1650 .push(Filter::EndsWith(field.into(), value.into()));
1651 self
1652 }
1653
1654 #[inline]
1656 pub fn is_null<F>(mut self, field: F) -> Self
1657 where
1658 F: Into<FieldName>,
1659 {
1660 self.filters.push(Filter::IsNull(field.into()));
1661 self
1662 }
1663
1664 #[inline]
1666 pub fn is_not_null<F>(mut self, field: F) -> Self
1667 where
1668 F: Into<FieldName>,
1669 {
1670 self.filters.push(Filter::IsNotNull(field.into()));
1671 self
1672 }
1673
1674 #[inline]
1676 pub fn filter(mut self, filter: Filter) -> Self {
1677 if !filter.is_none() {
1678 self.filters.push(filter);
1679 }
1680 self
1681 }
1682
1683 #[inline]
1685 pub fn filter_if(self, condition: bool, filter: Filter) -> Self {
1686 if condition { self.filter(filter) } else { self }
1687 }
1688
1689 #[inline]
1691 pub fn filter_if_some<F>(self, opt: Option<F>) -> Self
1692 where
1693 F: Into<Filter>,
1694 {
1695 match opt {
1696 Some(f) => self.filter(f.into()),
1697 None => self,
1698 }
1699 }
1700
1701 #[inline]
1703 pub fn build_and(self) -> Filter {
1704 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1705 match filters.len() {
1706 0 => Filter::None,
1707 1 => filters.into_iter().next().unwrap(),
1708 _ => Filter::And(filters.into_boxed_slice()),
1709 }
1710 }
1711
1712 #[inline]
1714 pub fn build_or(self) -> Filter {
1715 let filters: Vec<_> = self.filters.into_iter().filter(|f| !f.is_none()).collect();
1716 match filters.len() {
1717 0 => Filter::None,
1718 1 => filters.into_iter().next().unwrap(),
1719 _ => Filter::Or(filters.into_boxed_slice()),
1720 }
1721 }
1722
1723 #[inline]
1725 pub fn len(&self) -> usize {
1726 self.filters.len()
1727 }
1728
1729 #[inline]
1731 pub fn is_empty(&self) -> bool {
1732 self.filters.is_empty()
1733 }
1734}
1735
1736impl Default for FluentFilterBuilder {
1737 fn default() -> Self {
1738 Self::new()
1739 }
1740}
1741
1742#[cfg(test)]
1743mod tests {
1744 use super::*;
1745
1746 #[test]
1747 fn test_filter_value_from() {
1748 assert_eq!(FilterValue::from(42i32), FilterValue::Int(42));
1749 assert_eq!(
1750 FilterValue::from("hello"),
1751 FilterValue::String("hello".to_string())
1752 );
1753 assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
1754 }
1755
1756 #[test]
1757 fn test_scalar_filter_equals() {
1758 let filter = ScalarFilter::Equals("test@example.com".to_string()).into_filter("email");
1759
1760 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1761 assert_eq!(sql, r#""email" = $1"#);
1762 assert_eq!(params.len(), 1);
1763 }
1764
1765 #[test]
1766 fn test_filter_and() {
1767 let f1 = Filter::Equals("name".into(), "Alice".into());
1768 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
1769 let combined = Filter::and([f1, f2]);
1770
1771 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
1772 assert!(sql.contains("AND"));
1773 assert_eq!(params.len(), 2);
1774 }
1775
1776 #[test]
1777 fn test_filter_or() {
1778 let f1 = Filter::Equals("status".into(), "active".into());
1779 let f2 = Filter::Equals("status".into(), "pending".into());
1780 let combined = Filter::or([f1, f2]);
1781
1782 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
1783 assert!(sql.contains("OR"));
1784 }
1785
1786 #[test]
1787 fn test_filter_not() {
1788 let filter = Filter::not(Filter::Equals("deleted".into(), FilterValue::Bool(true)));
1789
1790 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
1791 assert!(sql.contains("NOT"));
1792 }
1793
1794 #[test]
1795 fn test_filter_is_null() {
1796 let filter = Filter::IsNull("deleted_at".into());
1797 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1798 assert_eq!(sql, r#""deleted_at" IS NULL"#);
1799 assert!(params.is_empty());
1800 }
1801
1802 #[test]
1803 fn test_filter_in() {
1804 let filter = Filter::In("status".into(), vec!["active".into(), "pending".into()]);
1805 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1806 assert!(sql.contains("IN"));
1807 assert_eq!(params.len(), 2);
1808 }
1809
1810 #[test]
1811 fn test_filter_contains() {
1812 let filter = Filter::Contains("email".into(), "example".into());
1813 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1814 assert!(sql.contains("LIKE"));
1815 assert!(sql.contains(r"ESCAPE '\'"));
1816 assert_eq!(params.len(), 1);
1817 if let FilterValue::String(s) = ¶ms[0] {
1818 assert!(s.contains("%example%"));
1819 }
1820 }
1821
1822 #[test]
1823 fn test_filter_like_escapes_wildcards() {
1824 let filter = Filter::Contains("name".into(), "100%".into());
1826 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1827 assert!(sql.contains(r"ESCAPE '\'"));
1828 assert_eq!(params.len(), 1);
1829 assert_eq!(params[0], FilterValue::String(r"%100\%%".to_string()));
1830
1831 let filter = Filter::StartsWith("name".into(), "a_b".into());
1833 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1834 assert!(sql.contains(r"ESCAPE '\'"));
1835 assert_eq!(params.len(), 1);
1836 assert_eq!(params[0], FilterValue::String(r"a\_b%".to_string()));
1837
1838 let filter = Filter::EndsWith("path".into(), r"a\b".into());
1840 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1841 assert!(sql.contains(r"ESCAPE '\'"));
1842 assert_eq!(params.len(), 1);
1843 assert_eq!(params[0], FilterValue::String(r"%a\\b".to_string()));
1844 }
1845
1846 #[test]
1847 fn test_filter_like_escape_clause_mysql() {
1848 use crate::dialect::Mysql;
1852
1853 let filter = Filter::Contains("name".into(), "100%".into());
1854 let (sql, params) = filter.to_sql(0, &Mysql);
1855 assert!(sql.contains(r"ESCAPE '\\'"), "MySQL escape clause: {sql}");
1856 assert!(!sql.contains(r"ESCAPE '\'"), "MySQL escape clause: {sql}");
1857 assert_eq!(params.len(), 1);
1858 assert_eq!(params[0], FilterValue::String(r"%100\%%".to_string()));
1859
1860 let filter = Filter::StartsWith("name".into(), "a_b".into());
1862 let (sql, _) = filter.to_sql(0, &Mysql);
1863 assert!(sql.contains(r"ESCAPE '\\'"), "MySQL escape clause: {sql}");
1864
1865 let filter = Filter::EndsWith("name".into(), "a_b".into());
1866 let (sql, _) = filter.to_sql(0, &Mysql);
1867 assert!(sql.contains(r"ESCAPE '\\'"), "MySQL escape clause: {sql}");
1868 }
1869
1870 #[test]
1873 fn test_filter_value_is_null() {
1874 assert!(FilterValue::Null.is_null());
1875 assert!(!FilterValue::Bool(false).is_null());
1876 assert!(!FilterValue::Int(0).is_null());
1877 assert!(!FilterValue::Float(0.0).is_null());
1878 assert!(!FilterValue::String("".to_string()).is_null());
1879 }
1880
1881 #[test]
1882 fn test_filter_value_from_i64() {
1883 assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
1884 assert_eq!(FilterValue::from(-100i64), FilterValue::Int(-100));
1885 }
1886
1887 #[test]
1888 #[allow(clippy::approx_constant)]
1889 fn test_filter_value_from_f64() {
1890 assert_eq!(FilterValue::from(3.14f64), FilterValue::Float(3.14));
1891 }
1892
1893 #[test]
1894 fn test_filter_value_from_string() {
1895 assert_eq!(
1896 FilterValue::from("hello".to_string()),
1897 FilterValue::String("hello".to_string())
1898 );
1899 }
1900
1901 #[test]
1902 fn test_filter_value_from_vec() {
1903 let values: Vec<i32> = vec![1, 2, 3];
1904 let filter_val: FilterValue = values.into();
1905 if let FilterValue::List(list) = filter_val {
1906 assert_eq!(list.len(), 3);
1907 assert_eq!(list[0], FilterValue::Int(1));
1908 assert_eq!(list[1], FilterValue::Int(2));
1909 assert_eq!(list[2], FilterValue::Int(3));
1910 } else {
1911 panic!("Expected List");
1912 }
1913 }
1914
1915 #[test]
1916 fn test_filter_value_from_option_some() {
1917 let val: FilterValue = Some(42i32).into();
1918 assert_eq!(val, FilterValue::Int(42));
1919 }
1920
1921 #[test]
1922 fn test_filter_value_from_option_none() {
1923 let val: FilterValue = Option::<i32>::None.into();
1924 assert_eq!(val, FilterValue::Null);
1925 }
1926
1927 #[test]
1930 fn test_scalar_filter_not() {
1931 let filter = ScalarFilter::Not(Box::new("test".to_string())).into_filter("name");
1932 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1933 assert_eq!(sql, r#""name" != $1"#);
1934 assert_eq!(params.len(), 1);
1935 }
1936
1937 #[test]
1938 fn test_scalar_filter_in() {
1939 let filter = ScalarFilter::In(vec!["a".to_string(), "b".to_string()]).into_filter("status");
1940 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1941 assert!(sql.contains("IN"));
1942 assert_eq!(params.len(), 2);
1943 }
1944
1945 #[test]
1946 fn test_scalar_filter_not_in() {
1947 let filter = ScalarFilter::NotIn(vec!["x".to_string()]).into_filter("status");
1948 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1949 assert!(sql.contains("NOT IN"));
1950 assert_eq!(params.len(), 1);
1951 }
1952
1953 #[test]
1954 fn test_scalar_filter_lt() {
1955 let filter = ScalarFilter::Lt(100i32).into_filter("price");
1956 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1957 assert_eq!(sql, r#""price" < $1"#);
1958 assert_eq!(params.len(), 1);
1959 }
1960
1961 #[test]
1962 fn test_scalar_filter_lte() {
1963 let filter = ScalarFilter::Lte(100i32).into_filter("price");
1964 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1965 assert_eq!(sql, r#""price" <= $1"#);
1966 assert_eq!(params.len(), 1);
1967 }
1968
1969 #[test]
1970 fn test_scalar_filter_gt() {
1971 let filter = ScalarFilter::Gt(0i32).into_filter("quantity");
1972 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1973 assert_eq!(sql, r#""quantity" > $1"#);
1974 assert_eq!(params.len(), 1);
1975 }
1976
1977 #[test]
1978 fn test_scalar_filter_gte() {
1979 let filter = ScalarFilter::Gte(0i32).into_filter("quantity");
1980 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1981 assert_eq!(sql, r#""quantity" >= $1"#);
1982 assert_eq!(params.len(), 1);
1983 }
1984
1985 #[test]
1986 fn test_scalar_filter_starts_with() {
1987 let filter = ScalarFilter::StartsWith("prefix".to_string()).into_filter("name");
1988 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
1989 assert!(sql.contains("LIKE"));
1990 assert_eq!(params.len(), 1);
1991 if let FilterValue::String(s) = ¶ms[0] {
1992 assert!(s.starts_with("prefix"));
1993 assert!(s.ends_with("%"));
1994 }
1995 }
1996
1997 #[test]
1998 fn test_scalar_filter_ends_with() {
1999 let filter = ScalarFilter::EndsWith("suffix".to_string()).into_filter("name");
2000 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2001 assert!(sql.contains("LIKE"));
2002 assert_eq!(params.len(), 1);
2003 if let FilterValue::String(s) = ¶ms[0] {
2004 assert!(s.starts_with("%"));
2005 assert!(s.ends_with("suffix"));
2006 }
2007 }
2008
2009 #[test]
2010 fn test_scalar_filter_is_null() {
2011 let filter = ScalarFilter::<String>::IsNull.into_filter("deleted_at");
2012 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2013 assert_eq!(sql, r#""deleted_at" IS NULL"#);
2014 assert!(params.is_empty());
2015 }
2016
2017 #[test]
2018 fn test_scalar_filter_is_not_null() {
2019 let filter = ScalarFilter::<String>::IsNotNull.into_filter("name");
2020 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2021 assert_eq!(sql, r#""name" IS NOT NULL"#);
2022 assert!(params.is_empty());
2023 }
2024
2025 #[test]
2028 fn test_filter_none() {
2029 let filter = Filter::none();
2030 assert!(filter.is_none());
2031 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2032 assert_eq!(sql, "TRUE"); assert!(params.is_empty());
2034 }
2035
2036 #[test]
2037 fn test_filter_not_equals() {
2038 let filter = Filter::NotEquals("status".into(), "deleted".into());
2039 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2040 assert_eq!(sql, r#""status" != $1"#);
2041 assert_eq!(params.len(), 1);
2042 }
2043
2044 #[test]
2045 fn test_filter_lte() {
2046 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
2047 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2048 assert_eq!(sql, r#""price" <= $1"#);
2049 assert_eq!(params.len(), 1);
2050 }
2051
2052 #[test]
2053 fn test_filter_gte() {
2054 let filter = Filter::Gte("quantity".into(), FilterValue::Int(0));
2055 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2056 assert_eq!(sql, r#""quantity" >= $1"#);
2057 assert_eq!(params.len(), 1);
2058 }
2059
2060 #[test]
2061 fn test_filter_not_in() {
2062 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
2063 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2064 assert!(sql.contains("NOT IN"));
2065 assert_eq!(params.len(), 2);
2066 }
2067
2068 #[test]
2069 fn test_filter_starts_with() {
2070 let filter = Filter::StartsWith("email".into(), "admin".into());
2071 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2072 assert!(sql.contains("LIKE"));
2073 assert_eq!(params.len(), 1);
2074 }
2075
2076 #[test]
2077 fn test_filter_ends_with() {
2078 let filter = Filter::EndsWith("email".into(), "@example.com".into());
2079 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2080 assert!(sql.contains("LIKE"));
2081 assert_eq!(params.len(), 1);
2082 }
2083
2084 #[test]
2085 fn test_filter_is_not_null() {
2086 let filter = Filter::IsNotNull("name".into());
2087 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2088 assert_eq!(sql, r#""name" IS NOT NULL"#);
2089 assert!(params.is_empty());
2090 }
2091
2092 #[test]
2095 fn test_filter_and_empty() {
2096 let filter = Filter::and([]);
2097 assert!(filter.is_none());
2098 }
2099
2100 #[test]
2101 fn test_filter_and_single() {
2102 let f = Filter::Equals("name".into(), "Alice".into());
2103 let combined = Filter::and([f.clone()]);
2104 assert_eq!(combined, f);
2105 }
2106
2107 #[test]
2108 fn test_filter_and_with_none() {
2109 let f1 = Filter::Equals("name".into(), "Alice".into());
2110 let f2 = Filter::None;
2111 let combined = Filter::and([f1.clone(), f2]);
2112 assert_eq!(combined, f1);
2113 }
2114
2115 #[test]
2116 fn test_filter_or_empty() {
2117 let filter = Filter::or([]);
2118 assert!(filter.is_none());
2119 }
2120
2121 #[test]
2122 fn test_filter_or_single() {
2123 let f = Filter::Equals("status".into(), "active".into());
2124 let combined = Filter::or([f.clone()]);
2125 assert_eq!(combined, f);
2126 }
2127
2128 #[test]
2129 fn test_filter_or_with_none() {
2130 let f1 = Filter::Equals("status".into(), "active".into());
2131 let f2 = Filter::None;
2132 let combined = Filter::or([f1.clone(), f2]);
2133 assert_eq!(combined, f1);
2134 }
2135
2136 #[test]
2137 fn test_filter_not_none() {
2138 let filter = Filter::not(Filter::None);
2139 assert!(filter.is_none());
2140 }
2141
2142 #[test]
2143 fn test_filter_and_then() {
2144 let f1 = Filter::Equals("name".into(), "Alice".into());
2145 let f2 = Filter::Gt("age".into(), FilterValue::Int(18));
2146 let combined = f1.and_then(f2);
2147
2148 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2149 assert!(sql.contains("AND"));
2150 assert_eq!(params.len(), 2);
2151 }
2152
2153 #[test]
2154 fn test_filter_and_then_with_none_first() {
2155 let f1 = Filter::None;
2156 let f2 = Filter::Equals("name".into(), "Bob".into());
2157 let combined = f1.and_then(f2.clone());
2158 assert_eq!(combined, f2);
2159 }
2160
2161 #[test]
2162 fn test_filter_and_then_with_none_second() {
2163 let f1 = Filter::Equals("name".into(), "Alice".into());
2164 let f2 = Filter::None;
2165 let combined = f1.clone().and_then(f2);
2166 assert_eq!(combined, f1);
2167 }
2168
2169 #[test]
2170 fn test_filter_and_then_chained() {
2171 let f1 = Filter::Equals("a".into(), "1".into());
2172 let f2 = Filter::Equals("b".into(), "2".into());
2173 let f3 = Filter::Equals("c".into(), "3".into());
2174 let combined = f1.and_then(f2).and_then(f3);
2175
2176 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2177 assert!(sql.contains("AND"));
2178 assert_eq!(params.len(), 3);
2179 }
2180
2181 #[test]
2182 fn test_filter_or_else() {
2183 let f1 = Filter::Equals("status".into(), "active".into());
2184 let f2 = Filter::Equals("status".into(), "pending".into());
2185 let combined = f1.or_else(f2);
2186
2187 let (sql, _) = combined.to_sql(0, &crate::dialect::Postgres);
2188 assert!(sql.contains("OR"));
2189 }
2190
2191 #[test]
2192 fn test_filter_or_else_with_none_first() {
2193 let f1 = Filter::None;
2194 let f2 = Filter::Equals("name".into(), "Bob".into());
2195 let combined = f1.or_else(f2.clone());
2196 assert_eq!(combined, f2);
2197 }
2198
2199 #[test]
2200 fn test_filter_or_else_with_none_second() {
2201 let f1 = Filter::Equals("name".into(), "Alice".into());
2202 let f2 = Filter::None;
2203 let combined = f1.clone().or_else(f2);
2204 assert_eq!(combined, f1);
2205 }
2206
2207 #[test]
2210 fn test_filter_nested_and_or() {
2211 let f1 = Filter::Equals("status".into(), "active".into());
2212 let f2 = Filter::and([
2213 Filter::Gt("age".into(), FilterValue::Int(18)),
2214 Filter::Lt("age".into(), FilterValue::Int(65)),
2215 ]);
2216 let combined = Filter::and([f1, f2]);
2217
2218 let (sql, params) = combined.to_sql(0, &crate::dialect::Postgres);
2219 assert!(sql.contains("AND"));
2220 assert_eq!(params.len(), 3);
2221 }
2222
2223 #[test]
2224 fn test_filter_nested_not() {
2225 let inner = Filter::and([
2226 Filter::Equals("status".into(), "deleted".into()),
2227 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2228 ]);
2229 let filter = Filter::not(inner);
2230
2231 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2232 assert!(sql.contains("NOT"));
2233 assert!(sql.contains("AND"));
2234 assert_eq!(params.len(), 2);
2235 }
2236
2237 #[test]
2238 fn test_filter_with_json_value() {
2239 let json_val = serde_json::json!({"key": "value"});
2240 let filter = Filter::Equals("metadata".into(), FilterValue::Json(json_val));
2241 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2242 assert_eq!(sql, r#""metadata" = $1"#);
2243 assert_eq!(params.len(), 1);
2244 }
2245
2246 #[test]
2247 fn test_filter_in_empty_list() {
2248 let filter = Filter::In("status".into(), vec![]);
2249 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2250 assert!(
2252 sql.contains("FALSE")
2253 || sql.contains("1=0")
2254 || sql.is_empty()
2255 || sql.contains("status")
2256 );
2257 assert!(params.is_empty());
2258 }
2259
2260 #[test]
2261 fn test_filter_with_null_value() {
2262 let filter = Filter::IsNull("deleted_at".into());
2264 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2265 assert!(sql.contains("deleted_at"));
2266 assert!(sql.contains("IS NULL"));
2267 assert!(params.is_empty());
2268 }
2269
2270 #[test]
2273 fn test_and_builder_basic() {
2274 let filter = Filter::and_builder(3)
2275 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2276 .push(Filter::Gt("score".into(), FilterValue::Int(100)))
2277 .push(Filter::IsNotNull("email".into()))
2278 .build();
2279
2280 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2281 assert!(sql.contains("AND"));
2282 assert_eq!(params.len(), 2); }
2284
2285 #[test]
2286 fn test_and_builder_empty() {
2287 let filter = Filter::and_builder(0).build();
2288 assert!(filter.is_none());
2289 }
2290
2291 #[test]
2292 fn test_and_builder_single() {
2293 let filter = Filter::and_builder(1)
2294 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2295 .build();
2296
2297 assert!(matches!(filter, Filter::Equals(_, _)));
2299 }
2300
2301 #[test]
2302 fn test_and_builder_filters_none() {
2303 let filter = Filter::and_builder(3)
2304 .push(Filter::None)
2305 .push(Filter::Equals("id".into(), FilterValue::Int(1)))
2306 .push(Filter::None)
2307 .build();
2308
2309 assert!(matches!(filter, Filter::Equals(_, _)));
2311 }
2312
2313 #[test]
2314 fn test_and_builder_push_if() {
2315 let include_deleted = false;
2316 let filter = Filter::and_builder(2)
2317 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2318 .push_if(include_deleted, Filter::IsNull("deleted_at".into()))
2319 .build();
2320
2321 assert!(matches!(filter, Filter::Equals(_, _)));
2323 }
2324
2325 #[test]
2326 fn test_or_builder_basic() {
2327 let filter = Filter::or_builder(2)
2328 .push(Filter::Equals(
2329 "role".into(),
2330 FilterValue::String("admin".into()),
2331 ))
2332 .push(Filter::Equals(
2333 "role".into(),
2334 FilterValue::String("moderator".into()),
2335 ))
2336 .build();
2337
2338 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2339 assert!(sql.contains("OR"));
2340 }
2341
2342 #[test]
2343 fn test_or_builder_empty() {
2344 let filter = Filter::or_builder(0).build();
2345 assert!(filter.is_none());
2346 }
2347
2348 #[test]
2349 fn test_or_builder_single() {
2350 let filter = Filter::or_builder(1)
2351 .push(Filter::Equals("id".into(), FilterValue::Int(42)))
2352 .build();
2353
2354 assert!(matches!(filter, Filter::Equals(_, _)));
2356 }
2357
2358 #[test]
2359 fn test_fluent_builder_and() {
2360 let filter = Filter::builder()
2361 .eq("status", "active")
2362 .gt("age", 18)
2363 .is_not_null("email")
2364 .build_and();
2365
2366 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2367 assert!(sql.contains("AND"));
2368 assert_eq!(params.len(), 2);
2369 }
2370
2371 #[test]
2372 fn test_fluent_builder_or() {
2373 let filter = Filter::builder()
2374 .eq("role", "admin")
2375 .eq("role", "moderator")
2376 .build_or();
2377
2378 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2379 assert!(sql.contains("OR"));
2380 }
2381
2382 #[test]
2383 fn test_fluent_builder_with_capacity() {
2384 let filter = Filter::builder()
2385 .with_capacity(5)
2386 .eq("a", 1)
2387 .ne("b", 2)
2388 .lt("c", 3)
2389 .lte("d", 4)
2390 .gte("e", 5)
2391 .build_and();
2392
2393 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2394 assert!(sql.contains("AND"));
2395 assert_eq!(params.len(), 5);
2396 }
2397
2398 #[test]
2399 fn test_fluent_builder_string_operations() {
2400 let filter = Filter::builder()
2401 .contains("name", "john")
2402 .starts_with("email", "admin")
2403 .ends_with("domain", ".com")
2404 .build_and();
2405
2406 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2407 assert!(sql.contains("LIKE"));
2408 }
2409
2410 #[test]
2411 fn test_fluent_builder_null_operations() {
2412 let filter = Filter::builder()
2413 .is_null("deleted_at")
2414 .is_not_null("created_at")
2415 .build_and();
2416
2417 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2418 assert!(sql.contains("IS NULL"));
2419 assert!(sql.contains("IS NOT NULL"));
2420 }
2421
2422 #[test]
2423 fn test_fluent_builder_in_operations() {
2424 let filter = Filter::builder()
2425 .is_in("status", vec!["pending", "processing"])
2426 .not_in("role", vec!["banned", "suspended"])
2427 .build_and();
2428
2429 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2430 assert!(sql.contains("IN"));
2431 assert!(sql.contains("NOT IN"));
2432 }
2433
2434 #[test]
2435 fn test_fluent_builder_filter_if() {
2436 let include_archived = false;
2437 let filter = Filter::builder()
2438 .eq("active", true)
2439 .filter_if(
2440 include_archived,
2441 Filter::Equals("archived".into(), FilterValue::Bool(true)),
2442 )
2443 .build_and();
2444
2445 assert!(matches!(filter, Filter::Equals(_, _)));
2447 }
2448
2449 #[test]
2450 fn test_fluent_builder_filter_if_some() {
2451 let maybe_status: Option<Filter> = Some(Filter::Equals("status".into(), "active".into()));
2452 let filter = Filter::builder()
2453 .eq("id", 1)
2454 .filter_if_some(maybe_status)
2455 .build_and();
2456
2457 assert!(matches!(filter, Filter::And(_)));
2458 }
2459
2460 #[test]
2461 fn test_and_builder_extend() {
2462 let extra_filters = vec![
2463 Filter::Gt("score".into(), FilterValue::Int(100)),
2464 Filter::Lt("score".into(), FilterValue::Int(1000)),
2465 ];
2466
2467 let filter = Filter::and_builder(3)
2468 .push(Filter::Equals("active".into(), FilterValue::Bool(true)))
2469 .extend(extra_filters)
2470 .build();
2471
2472 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2473 assert!(sql.contains("AND"));
2474 assert_eq!(params.len(), 3);
2475 }
2476
2477 #[test]
2478 fn test_builder_len_and_is_empty() {
2479 let mut builder = AndFilterBuilder::new();
2480 assert!(builder.is_empty());
2481 assert_eq!(builder.len(), 0);
2482
2483 builder = builder.push(Filter::Equals("id".into(), FilterValue::Int(1)));
2484 assert!(!builder.is_empty());
2485 assert_eq!(builder.len(), 1);
2486 }
2487
2488 #[test]
2491 fn test_and2_both_valid() {
2492 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2493 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2494 let filter = Filter::and2(a, b);
2495
2496 assert!(matches!(filter, Filter::And(_)));
2497 let (sql, params) = filter.to_sql(0, &crate::dialect::Postgres);
2498 assert!(sql.contains("AND"));
2499 assert_eq!(params.len(), 2);
2500 }
2501
2502 #[test]
2503 fn test_and2_first_none() {
2504 let a = Filter::None;
2505 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2506 let filter = Filter::and2(a, b.clone());
2507
2508 assert_eq!(filter, b);
2509 }
2510
2511 #[test]
2512 fn test_and2_second_none() {
2513 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2514 let b = Filter::None;
2515 let filter = Filter::and2(a.clone(), b);
2516
2517 assert_eq!(filter, a);
2518 }
2519
2520 #[test]
2521 fn test_and2_both_none() {
2522 let filter = Filter::and2(Filter::None, Filter::None);
2523 assert!(filter.is_none());
2524 }
2525
2526 #[test]
2527 fn test_or2_both_valid() {
2528 let a = Filter::Equals("role".into(), FilterValue::String("admin".into()));
2529 let b = Filter::Equals("role".into(), FilterValue::String("mod".into()));
2530 let filter = Filter::or2(a, b);
2531
2532 assert!(matches!(filter, Filter::Or(_)));
2533 let (sql, _) = filter.to_sql(0, &crate::dialect::Postgres);
2534 assert!(sql.contains("OR"));
2535 }
2536
2537 #[test]
2538 fn test_or2_first_none() {
2539 let a = Filter::None;
2540 let b = Filter::Equals("active".into(), FilterValue::Bool(true));
2541 let filter = Filter::or2(a, b.clone());
2542
2543 assert_eq!(filter, b);
2544 }
2545
2546 #[test]
2547 fn test_or2_second_none() {
2548 let a = Filter::Equals("id".into(), FilterValue::Int(1));
2549 let b = Filter::None;
2550 let filter = Filter::or2(a.clone(), b);
2551
2552 assert_eq!(filter, a);
2553 }
2554
2555 #[test]
2556 fn test_or2_both_none() {
2557 let filter = Filter::or2(Filter::None, Filter::None);
2558 assert!(filter.is_none());
2559 }
2560
2561 #[test]
2564 fn to_sql_quotes_column_names_against_injection() {
2565 use crate::dialect::{Mssql, Mysql, Postgres};
2566
2567 let filter = Filter::Equals(r#"id" OR 1=1--"#.into(), FilterValue::Int(1));
2569
2570 let (sql_pg, _) = filter.to_sql(0, &Postgres);
2571 assert!(
2572 sql_pg.starts_with(r#""id"" OR 1=1--" ="#),
2573 "postgres did not quote col; got: {sql_pg}"
2574 );
2575
2576 let (sql_my, _) = filter.to_sql(0, &Mysql);
2577 assert!(
2578 sql_my.starts_with(r#"`id" OR 1=1--` ="#),
2579 "mysql did not quote col; got: {sql_my}"
2580 );
2581
2582 let (sql_ms, _) = filter.to_sql(0, &Mssql);
2583 assert!(
2584 sql_ms.starts_with(r#"[id" OR 1=1--] ="#),
2585 "mssql did not quote col; got: {sql_ms}"
2586 );
2587 }
2588
2589 #[test]
2590 fn to_sql_quotes_in_list_column_names() {
2591 use crate::dialect::Postgres;
2592 let filter = Filter::In("id".into(), vec![FilterValue::Int(1), FilterValue::Int(2)]);
2593 let (sql, _) = filter.to_sql(0, &Postgres);
2594 assert!(
2595 sql.starts_with(r#""id" IN ("#),
2596 "expected quoted id on IN, got: {sql}"
2597 );
2598 }
2599
2600 #[test]
2601 fn to_sql_emits_sequential_placeholders() {
2602 use crate::dialect::Postgres;
2607
2608 let f = Filter::In(
2610 "id".into(),
2611 vec![
2612 FilterValue::Int(1),
2613 FilterValue::Int(2),
2614 FilterValue::Int(3),
2615 ],
2616 );
2617 let (sql, params) = f.to_sql(0, &Postgres);
2618 assert_eq!(sql, r#""id" IN ($1, $2, $3)"#, "IN placeholders: {sql}");
2619 assert_eq!(params.len(), 3);
2620
2621 let f = Filter::And(Box::new([
2623 Filter::Equals("a".into(), FilterValue::Int(10)),
2624 Filter::Equals("b".into(), FilterValue::Int(20)),
2625 ]));
2626 let (sql, params) = f.to_sql(0, &Postgres);
2627 assert_eq!(sql, r#"("a" = $1 AND "b" = $2)"#, "AND placeholders: {sql}");
2628 assert_eq!(params.len(), 2);
2629
2630 let f = Filter::And(Box::new([
2632 Filter::Equals("a".into(), FilterValue::Int(1)),
2633 Filter::In("b".into(), vec![FilterValue::Int(2), FilterValue::Int(3)]),
2634 ]));
2635 let (sql, params) = f.to_sql(0, &Postgres);
2636 assert_eq!(
2637 sql, r#"("a" = $1 AND "b" IN ($2, $3))"#,
2638 "mixed placeholders: {sql}"
2639 );
2640 assert_eq!(params.len(), 3);
2641
2642 let f = Filter::Equals("a".into(), FilterValue::Int(7));
2645 let (sql, _) = f.to_sql(5, &Postgres);
2646 assert_eq!(sql, r#""a" = $6"#, "offset placeholder: {sql}");
2647
2648 let f = Filter::NotIn(
2650 "status".into(),
2651 vec![
2652 FilterValue::String("deleted".into()),
2653 FilterValue::String("archived".into()),
2654 ],
2655 );
2656 let (sql, params) = f.to_sql(0, &Postgres);
2657 assert_eq!(
2658 sql, r#""status" NOT IN ($1, $2)"#,
2659 "NotIn placeholders: {sql}"
2660 );
2661 assert_eq!(params.len(), 2);
2662
2663 let f = Filter::Or(Box::new([
2665 Filter::Equals("a".into(), FilterValue::Int(100)),
2666 Filter::Equals("b".into(), FilterValue::Int(200)),
2667 ]));
2668 let (sql, params) = f.to_sql(0, &Postgres);
2669 assert_eq!(sql, r#"("a" = $1 OR "b" = $2)"#, "OR placeholders: {sql}");
2670 assert_eq!(params.len(), 2);
2671
2672 let f = Filter::StartsWith("name".into(), "admin".into());
2674 let (sql, params) = f.to_sql(0, &Postgres);
2675 assert_eq!(
2676 sql, r#""name" LIKE $1 ESCAPE '\'"#,
2677 "StartsWith placeholder: {sql}"
2678 );
2679 assert_eq!(params.len(), 1);
2680
2681 let f = Filter::And(Box::new([
2684 Filter::And(Box::new([
2685 Filter::Equals("a".into(), FilterValue::Int(1)),
2686 Filter::Equals("b".into(), FilterValue::Int(2)),
2687 ])),
2688 Filter::Equals("c".into(), FilterValue::Int(3)),
2689 ]));
2690 let (sql, params) = f.to_sql(0, &Postgres);
2691 assert_eq!(
2692 sql, r#"(("a" = $1 AND "b" = $2) AND "c" = $3)"#,
2693 "nested AND placeholders: {sql}"
2694 );
2695 assert_eq!(params.len(), 3);
2696
2697 let f = Filter::And(Box::new([
2699 Filter::Equals("a".into(), FilterValue::Int(10)),
2700 Filter::Or(Box::new([
2701 Filter::Equals("b".into(), FilterValue::Int(20)),
2702 Filter::Equals("c".into(), FilterValue::Int(30)),
2703 ])),
2704 ]));
2705 let (sql, params) = f.to_sql(0, &Postgres);
2706 assert_eq!(
2707 sql, r#"("a" = $1 AND ("b" = $2 OR "c" = $3))"#,
2708 "And-Or nesting: {sql}"
2709 );
2710 assert_eq!(params.len(), 3);
2711
2712 let f = Filter::In(
2714 "id".into(),
2715 vec![
2716 FilterValue::Int(1),
2717 FilterValue::Int(2),
2718 FilterValue::Int(3),
2719 ],
2720 );
2721 let (sql, params) = f.to_sql(5, &Postgres);
2722 assert_eq!(sql, r#""id" IN ($6, $7, $8)"#, "IN offset: {sql}");
2723 assert_eq!(params.len(), 3);
2724 }
2725
2726 #[test]
2727 fn to_sql_placeholders_are_dialect_specific() {
2728 use crate::dialect::{Mysql, Sqlite};
2731
2732 let in_filter = || {
2733 Filter::In(
2734 "id".into(),
2735 vec![
2736 FilterValue::Int(1),
2737 FilterValue::Int(2),
2738 FilterValue::Int(3),
2739 ],
2740 )
2741 };
2742
2743 let (sql, params) = in_filter().to_sql(0, &Sqlite);
2745 assert_eq!(sql, r#""id" IN (?1, ?2, ?3)"#, "SQLite IN: {sql}");
2746 assert_eq!(params.len(), 3);
2747
2748 let (sql, params) = in_filter().to_sql(0, &Mysql);
2750 assert_eq!(sql, "`id` IN (?, ?, ?)", "MySQL IN: {sql}");
2751 assert_eq!(params.len(), 3);
2752
2753 let f = Filter::And(Box::new([
2755 Filter::Equals("a".into(), FilterValue::Int(10)),
2756 Filter::Equals("b".into(), FilterValue::Int(20)),
2757 ]));
2758 let (sql, _) = f.to_sql(0, &Sqlite);
2759 assert_eq!(sql, r#"("a" = ?1 AND "b" = ?2)"#, "SQLite AND: {sql}");
2760 }
2761
2762 #[test]
2763 fn to_sql_quotes_null_checks() {
2764 use crate::dialect::Postgres;
2765 let filter = Filter::IsNull("deleted_at".into());
2766 let (sql, _) = filter.to_sql(0, &Postgres);
2767 assert_eq!(sql, r#""deleted_at" IS NULL"#);
2768 }
2769
2770 #[test]
2771 fn to_sql_quotes_comparison_operators() {
2772 use crate::dialect::Postgres;
2773
2774 let filter = Filter::Lt("age".into(), FilterValue::Int(18));
2775 let (sql, _) = filter.to_sql(0, &Postgres);
2776 assert!(sql.starts_with(r#""age" < "#), "Lt not quoted: {sql}");
2777
2778 let filter = Filter::Lte("price".into(), FilterValue::Int(100));
2779 let (sql, _) = filter.to_sql(0, &Postgres);
2780 assert!(sql.starts_with(r#""price" <= "#), "Lte not quoted: {sql}");
2781
2782 let filter = Filter::Gt("score".into(), FilterValue::Int(0));
2783 let (sql, _) = filter.to_sql(0, &Postgres);
2784 assert!(sql.starts_with(r#""score" > "#), "Gt not quoted: {sql}");
2785
2786 let filter = Filter::Gte("quantity".into(), FilterValue::Int(1));
2787 let (sql, _) = filter.to_sql(0, &Postgres);
2788 assert!(
2789 sql.starts_with(r#""quantity" >= "#),
2790 "Gte not quoted: {sql}"
2791 );
2792
2793 let filter = Filter::NotEquals("status".into(), "deleted".into());
2794 let (sql, _) = filter.to_sql(0, &Postgres);
2795 assert!(
2796 sql.starts_with(r#""status" != "#),
2797 "NotEquals not quoted: {sql}"
2798 );
2799 }
2800
2801 #[test]
2802 fn to_sql_quotes_like_operators() {
2803 use crate::dialect::Postgres;
2804
2805 let filter = Filter::Contains("email".into(), "example".into());
2806 let (sql, _) = filter.to_sql(0, &Postgres);
2807 assert!(
2808 sql.starts_with(r#""email" LIKE "#),
2809 "Contains not quoted: {sql}"
2810 );
2811
2812 let filter = Filter::StartsWith("name".into(), "admin".into());
2813 let (sql, _) = filter.to_sql(0, &Postgres);
2814 assert!(
2815 sql.starts_with(r#""name" LIKE "#),
2816 "StartsWith not quoted: {sql}"
2817 );
2818
2819 let filter = Filter::EndsWith("domain".into(), ".com".into());
2820 let (sql, _) = filter.to_sql(0, &Postgres);
2821 assert!(
2822 sql.starts_with(r#""domain" LIKE "#),
2823 "EndsWith not quoted: {sql}"
2824 );
2825 }
2826
2827 #[test]
2828 fn to_sql_quotes_not_in() {
2829 use crate::dialect::Postgres;
2830 let filter = Filter::NotIn("status".into(), vec!["deleted".into(), "archived".into()]);
2831 let (sql, _) = filter.to_sql(0, &Postgres);
2832 assert!(
2833 sql.starts_with(r#""status" NOT IN ("#),
2834 "NotIn not quoted: {sql}"
2835 );
2836 }
2837
2838 #[test]
2839 fn to_sql_quotes_is_not_null() {
2840 use crate::dialect::Postgres;
2841 let filter = Filter::IsNotNull("verified_at".into());
2842 let (sql, _) = filter.to_sql(0, &Postgres);
2843 assert_eq!(sql, r#""verified_at" IS NOT NULL"#);
2844 }
2845
2846 #[test]
2847 fn filter_value_from_u64_in_range() {
2848 assert_eq!(FilterValue::from(42u64), FilterValue::Int(42));
2849 assert_eq!(FilterValue::from(0u64), FilterValue::Int(0));
2850 let max_safe = i64::MAX as u64;
2851 assert_eq!(FilterValue::from(max_safe), FilterValue::Int(i64::MAX));
2852 }
2853
2854 #[test]
2855 #[should_panic(expected = "u64 value exceeds i64::MAX")]
2856 fn filter_value_from_u64_overflow_panics() {
2857 let _ = FilterValue::from(u64::MAX);
2858 }
2859
2860 #[test]
2861 fn filter_value_from_chrono_datetime_utc_rfc3339() {
2862 use chrono::{TimeZone, Utc};
2863 let dt = Utc.with_ymd_and_hms(2020, 1, 15, 10, 30, 45).unwrap();
2864 let fv = FilterValue::from(dt);
2865 assert_eq!(
2866 fv,
2867 FilterValue::String("2020-01-15T10:30:45.000000Z".to_string())
2868 );
2869 }
2870
2871 #[test]
2872 fn filter_value_from_chrono_naive_datetime_iso() {
2873 use chrono::NaiveDate;
2874 let dt = NaiveDate::from_ymd_opt(2020, 1, 15)
2875 .unwrap()
2876 .and_hms_opt(10, 30, 45)
2877 .unwrap();
2878 let fv = FilterValue::from(dt);
2879 assert_eq!(
2880 fv,
2881 FilterValue::String("2020-01-15T10:30:45.000000".to_string())
2882 );
2883 }
2884
2885 #[test]
2886 fn filter_value_from_chrono_naive_date() {
2887 use chrono::NaiveDate;
2888 let d = NaiveDate::from_ymd_opt(2020, 1, 15).unwrap();
2889 assert_eq!(
2890 FilterValue::from(d),
2891 FilterValue::String("2020-01-15".to_string())
2892 );
2893 }
2894
2895 #[test]
2896 fn filter_value_from_chrono_naive_time() {
2897 use chrono::NaiveTime;
2898 let t = NaiveTime::from_hms_opt(10, 30, 45).unwrap();
2899 assert_eq!(
2900 FilterValue::from(t),
2901 FilterValue::String("10:30:45.000000".to_string())
2902 );
2903 }
2904
2905 #[test]
2912 fn filter_value_from_uuid_is_lowercase_hyphenated() {
2913 use uuid::Uuid;
2918 let u = Uuid::parse_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
2919 match FilterValue::from(u) {
2920 FilterValue::String(ref s) => {
2921 assert_eq!(s, "550e8400-e29b-41d4-a716-446655440000");
2922 assert_eq!(s, &u.to_string());
2923 }
2924 other => panic!("expected FilterValue::String, got {other:?}"),
2925 }
2926 }
2927
2928 #[test]
2929 fn filter_value_from_uuid_nil_round_trips() {
2930 use uuid::Uuid;
2931 let u = Uuid::nil();
2932 assert_eq!(
2933 FilterValue::from(u),
2934 FilterValue::String("00000000-0000-0000-0000-000000000000".to_string())
2935 );
2936 }
2937
2938 #[test]
2939 fn filter_value_from_decimal_uses_to_string_not_f64() {
2940 use rust_decimal::Decimal;
2944 use std::str::FromStr;
2945 let d = Decimal::from_str("3.14").unwrap();
2946 assert_eq!(
2947 FilterValue::from(d),
2948 FilterValue::String("3.14".to_string())
2949 );
2950 }
2951
2952 #[test]
2953 fn filter_value_from_decimal_high_precision_preserved() {
2954 use rust_decimal::Decimal;
2955 use std::str::FromStr;
2956 let d = Decimal::from_str("1234567890.1234567890").unwrap();
2958 match FilterValue::from(d) {
2959 FilterValue::String(ref s) => {
2960 assert_eq!(s, "1234567890.1234567890");
2961 }
2962 other => panic!("expected FilterValue::String, got {other:?}"),
2963 }
2964 }
2965
2966 #[test]
2967 fn filter_value_from_serde_json_value_keeps_json_variant() {
2968 let v = serde_json::json!({"key": "value", "nested": [1, 2, 3]});
2969 match FilterValue::from(v.clone()) {
2970 FilterValue::Json(inner) => {
2971 assert_eq!(inner, v);
2972 }
2973 other => panic!("expected FilterValue::Json, got {other:?}"),
2974 }
2975 }
2976
2977 #[test]
2978 fn filter_value_from_serde_json_null_keeps_json_variant() {
2979 let v = serde_json::Value::Null;
2984 match FilterValue::from(v) {
2985 FilterValue::Json(serde_json::Value::Null) => {}
2986 other => panic!("expected FilterValue::Json(Null), got {other:?}"),
2987 }
2988 }
2989
2990 #[test]
2991 fn filter_value_from_option_none_maps_to_null() {
2992 let none_i32: Option<i32> = None;
2995 assert_eq!(FilterValue::from(none_i32), FilterValue::Null);
2996 let none_string: Option<String> = None;
2997 assert_eq!(FilterValue::from(none_string), FilterValue::Null);
2998 }
2999
3000 #[test]
3001 fn filter_value_from_signed_integer_extremes() {
3002 assert_eq!(FilterValue::from(i8::MIN), FilterValue::Int(i8::MIN as i64));
3005 assert_eq!(FilterValue::from(i8::MAX), FilterValue::Int(i8::MAX as i64));
3006 assert_eq!(
3007 FilterValue::from(i16::MIN),
3008 FilterValue::Int(i16::MIN as i64)
3009 );
3010 assert_eq!(
3011 FilterValue::from(i16::MAX),
3012 FilterValue::Int(i16::MAX as i64)
3013 );
3014 }
3015
3016 #[test]
3017 fn filter_value_from_unsigned_integer_extremes() {
3018 assert_eq!(FilterValue::from(u8::MAX), FilterValue::Int(u8::MAX as i64));
3021 assert_eq!(
3022 FilterValue::from(u16::MAX),
3023 FilterValue::Int(u16::MAX as i64)
3024 );
3025 assert_eq!(
3026 FilterValue::from(u32::MAX),
3027 FilterValue::Int(u32::MAX as i64)
3028 );
3029 assert_eq!(FilterValue::from(u32::MAX), FilterValue::Int(4_294_967_295));
3031 }
3032
3033 #[test]
3034 fn filter_value_from_f32_widens_to_f64() {
3035 let v: f32 = 1.5;
3040 assert_eq!(FilterValue::from(v), FilterValue::Float(1.5));
3041 }
3042
3043 #[test]
3050 fn to_filter_value_option_some_some() {
3051 let v: Option<i32> = Some(42);
3052 assert_eq!(v.to_filter_value(), FilterValue::Int(42));
3053 }
3054
3055 #[test]
3056 fn to_filter_value_option_none_is_null() {
3057 let v: Option<i32> = None;
3058 assert_eq!(v.to_filter_value(), FilterValue::Null);
3059 }
3060
3061 #[test]
3062 fn to_filter_value_uuid_is_string() {
3063 let id = uuid::Uuid::nil();
3064 assert_eq!(id.to_filter_value(), FilterValue::String(id.to_string()));
3065 }
3066
3067 #[test]
3068 fn to_filter_value_bool_is_bool() {
3069 assert_eq!(true.to_filter_value(), FilterValue::Bool(true));
3070 }
3071
3072 #[test]
3073 fn scalar_subquery_lowers_to_inline_sql_with_dialect_placeholders() {
3074 use crate::dialect::Postgres;
3075 let f = Filter::ScalarSubquery {
3076 sql: Cow::Borrowed(
3077 "(SELECT COUNT(*) FROM posts p WHERE p.author_id = users.id AND p.published = {0}) > {1}",
3078 ),
3079 params: vec![FilterValue::Bool(true), FilterValue::Int(5)],
3080 };
3081 let (sql, params) = f.to_sql(0, &Postgres);
3082 assert_eq!(
3083 sql,
3084 "((SELECT COUNT(*) FROM posts p WHERE p.author_id = users.id AND p.published = $1) > $2)"
3085 );
3086 assert_eq!(params, vec![FilterValue::Bool(true), FilterValue::Int(5)]);
3087 }
3088
3089 #[test]
3090 fn scalar_subquery_offsets_placeholders_inside_and() {
3091 use crate::dialect::Postgres;
3092 let f = Filter::and([
3093 Filter::Equals("active".into(), FilterValue::Bool(true)),
3094 Filter::ScalarSubquery {
3095 sql: Cow::Borrowed(
3096 "(SELECT COUNT(*) FROM posts p WHERE p.author_id = users.id) >= {0}",
3097 ),
3098 params: vec![FilterValue::Int(1)],
3099 },
3100 ]);
3101 let (sql, params) = f.to_sql(0, &Postgres);
3102 assert!(sql.contains("$1"));
3104 assert!(sql.contains("$2"));
3105 assert_eq!(params.len(), 2);
3106 assert_eq!(params[0], FilterValue::Bool(true));
3107 assert_eq!(params[1], FilterValue::Int(1));
3108 }
3109
3110 #[test]
3111 fn scalar_subquery_handles_out_of_order_and_repeated_placeholders() {
3112 use crate::dialect::Postgres;
3113 let f = Filter::ScalarSubquery {
3114 sql: Cow::Borrowed("{1} = {0} AND {1} > {0}"),
3115 params: vec![FilterValue::Int(1), FilterValue::Int(2)],
3116 };
3117 let (sql, params) = f.to_sql(0, &Postgres);
3118 assert_eq!(sql, "($2 = $1 AND $2 > $1)");
3121 assert_eq!(params, vec![FilterValue::Int(1), FilterValue::Int(2)]);
3122 }
3123}