Skip to main content

sqlx_data_params/
filter.rs

1use std::borrow::Cow;
2use crate::{IntoParams, Params};
3
4#[derive(Clone, Debug, PartialEq, Eq)]
5pub enum FilterOperator {
6    Eq,
7    Ne,
8    Gt,
9    Lt,
10    Gte,
11    Lte,
12    /// Safe LIKE operator - automatically escapes special characters (% and _) to treat them as literals.
13    /// Use this for user input to prevent wildcard injection.
14    /// Example: searching "test_file%" will match exactly "test_file%", not use % and _ as wildcards.
15    Like,
16    /// Case-insensitive LIKE operator - automatically escapes special characters.
17    /// Similar to Like but case-insensitive. Safe for user input.
18    ILike,
19    /// Unsafe LIKE operator - allows intentional wildcards (% and _) in patterns.
20    /// Use only with controlled input where you intentionally want wildcard behavior.
21    /// Example: "user_%" will match "user_123", "user_abc", etc.
22    /// WARNING: Never use with direct user input due to wildcard injection risk.
23    UnsafeLike,
24    In,
25    NotIn,
26    IsNull,
27    IsNotNull,
28    Between,
29    /// Safe contains operator - wraps value in % wildcards and escapes special characters.
30    /// Ideal for substring searches. Always safe for user input.
31    Contains,
32}
33
34#[derive(Clone, Debug, PartialEq)]
35pub enum FilterValue {
36    String(Cow<'static, str>),
37    Int(i64),
38    UInt(u64),
39    Float(f64),
40    Bool(bool),
41    Array(Vec<FilterValue>),
42    // DateTime(String), // RFC3339 - fallback string format
43    // Date(String), // YYYY-MM-DD - fallback string format
44
45    // JSON support with native types
46    #[cfg(feature = "json")]
47    Json(sqlx_data_integration::JsonValue),
48
49    // UUID support - native SQLx re-exported types
50    #[cfg(feature = "uuid")]
51    Uuid(sqlx_data_integration::Uuid),
52    
53    // DateTime support with SQLx re-exported types
54    #[cfg(feature = "chrono")]
55    DateTimeChrono(sqlx_data_integration::DateTime),
56    #[cfg(feature = "chrono")]
57    NaiveDateTime(sqlx_data_integration::NaiveDateTime),
58    #[cfg(feature = "chrono")]
59    NaiveDate(sqlx_data_integration::NaiveDate),
60    #[cfg(feature = "chrono")]
61    NaiveTime(sqlx_data_integration::NaiveTime),
62
63    #[cfg(all(feature = "time", not(feature = "chrono")))]
64    OffsetDateTime(sqlx_data_integration::DateTime),
65    #[cfg(all(feature = "time", not(feature = "chrono")))]
66    PrimitiveDateTime(sqlx_data_integration::PrimitiveDateTime),
67    #[cfg(all(feature = "time", not(feature = "chrono")))]
68    Date(sqlx_data_integration::Date),
69    #[cfg(all(feature = "time", not(feature = "chrono")))]
70    Time(sqlx_data_integration::Time),
71
72    // Decimal/Money support with SQLx re-exported types
73    #[cfg(feature = "rust_decimal")]
74    Decimal(sqlx_data_integration::Decimal),
75    #[cfg(feature = "bigdecimal")]
76    Decimal(sqlx_data_integration::Decimal),
77
78    // Network types with SQLx re-exported types
79    #[cfg(feature = "ipnet")]
80    IpNet(sqlx_data_integration::IpNet),
81    #[cfg(feature = "ipnet")]
82    Ipv4Net(sqlx_data_integration::Ipv4Net),
83    #[cfg(feature = "ipnet")]
84    Ipv6Net(sqlx_data_integration::Ipv6Net),
85
86    #[cfg(feature = "ipnetwork")]
87    IpNetwork(sqlx_data_integration::IpNetwork),
88
89    // Hardware address with SQLx re-exported type
90    #[cfg(feature = "mac_address")]
91    MacAddress(sqlx_data_integration::MacAddress),
92
93    // Binary data types with SQLx re-exported types
94    #[cfg(feature = "bit-vec")]
95    BitVec(sqlx_data_integration::BitVec),
96    #[cfg(feature = "bstr")]
97    BStr(sqlx_data_integration::BString),
98
99    // Regular expressions
100    #[cfg(feature = "regexp")]
101    Regex(String), // Regex pattern as string for serialization
102
103    Blob(Vec<u8>),
104    Null,
105}
106
107#[derive(Clone, Debug, PartialEq)]
108pub struct Filter {
109    pub field: String,
110    pub operator: FilterOperator,
111    pub value: FilterValue,
112    /// Negates the filter operation. Always operates on the last applied filter.
113    /// Applies to: Like, UnsafeLike, ILike, In, Between.
114    /// Examples:
115    /// - Like with not=true becomes NOT LIKE
116    /// - In with not=true becomes NOT IN
117    /// - Between with not=true becomes NOT BETWEEN
118    ///
119    /// Usage: .like("name", "pattern").not() // Creates NOT LIKE
120    /// Note: IsNull/IsNotNull don't use this field as they are already explicit.
121    pub not: bool,
122}
123
124impl Filter {
125    pub fn new(
126        field: impl Into<String>,
127        operator: FilterOperator,
128        value: impl Into<FilterValue>,
129    ) -> Self {
130        Self {
131            field: field.into(),
132            operator,
133            value: value.into(),
134            not: false,
135        }
136    }
137}
138
139#[derive(Clone, Debug, PartialEq, Default)]
140pub struct FilterParams {
141    pub filters: Vec<Filter>,
142}
143
144impl IntoParams for FilterParams {
145    fn into_params(self) -> Params {
146        Params {
147            filters: Some(self),
148            search: None,
149            sort_by: None,
150            pagination: None,
151            limit: None,
152            offset: None,
153        }
154    }
155}
156
157// Implement From for common Rust types
158
159impl<'a, T> From<&'a [T]> for FilterValue
160where
161    T: Into<FilterValue> + Copy,
162{
163    fn from(slice: &'a [T]) -> Self {
164        FilterValue::Array(slice.iter().copied().map(Into::into).collect())
165    }
166}
167
168impl From<String> for FilterValue {
169    fn from(value: String) -> Self {
170        FilterValue::String(Cow::Owned(value))
171    }
172}
173
174impl From<&String> for FilterValue {
175    fn from(value: &String) -> Self {
176        FilterValue::String(Cow::Owned(value.to_owned()))
177    }
178}
179
180impl From<&str> for FilterValue {
181    fn from(value: &str) -> Self {
182        FilterValue::String(Cow::Owned(value.to_owned()))
183    }
184}
185
186impl From<i8> for FilterValue {
187    fn from(value: i8) -> Self {
188        FilterValue::Int(value as i64)
189    }
190}
191
192impl From<i16> for FilterValue {
193    fn from(value: i16) -> Self {
194        FilterValue::Int(value as i64)
195    }
196}
197
198impl From<i32> for FilterValue {
199    fn from(value: i32) -> Self {
200        FilterValue::Int(value as i64)
201    }
202}
203
204impl From<i64> for FilterValue {
205    fn from(value: i64) -> Self {
206        FilterValue::Int(value)
207    }
208}
209
210impl From<u8> for FilterValue {
211    fn from(value: u8) -> Self {
212        FilterValue::UInt(value as u64)
213    }
214}
215
216impl From<u16> for FilterValue {
217    fn from(value: u16) -> Self {
218        FilterValue::UInt(value as u64)
219    }
220}
221
222impl From<u32> for FilterValue {
223    fn from(value: u32) -> Self {
224        FilterValue::UInt(value as u64)
225    }
226}
227
228impl From<u64> for FilterValue {
229    fn from(value: u64) -> Self {
230        FilterValue::UInt(value)
231    }
232}
233
234impl From<f32> for FilterValue {
235    fn from(value: f32) -> Self {
236        FilterValue::Float(value as f64)
237    }
238}
239
240impl From<f64> for FilterValue {
241    fn from(value: f64) -> Self {
242        FilterValue::Float(value)
243    }
244}
245
246impl From<isize> for FilterValue {
247    fn from(value: isize) -> Self {
248        FilterValue::Int(value as i64)
249    }
250}
251
252impl From<usize> for FilterValue {
253    fn from(value: usize) -> Self {
254        FilterValue::UInt(value as u64)
255    }
256}
257
258impl From<bool> for FilterValue {
259    fn from(value: bool) -> Self {
260        FilterValue::Bool(value)
261    }
262}
263
264impl<T> From<Option<T>> for FilterValue
265where
266    T: Into<FilterValue>,
267{
268    fn from(value: Option<T>) -> Self {
269        match value {
270            Some(v) => v.into(),
271            None => FilterValue::Null,
272        }
273    }
274}
275
276impl<T> From<Vec<T>> for FilterValue
277where
278    T: Into<FilterValue>,
279{
280    fn from(vec: Vec<T>) -> Self {
281        FilterValue::Array(vec.into_iter().map(Into::into).collect())
282    }
283}
284
285impl<T, const N: usize> From<[T; N]> for FilterValue
286where
287    T: Into<FilterValue> + Copy,
288{
289    fn from(array: [T; N]) -> Self {
290        FilterValue::Array(array.iter().copied().map(Into::into).collect())
291    }
292}
293
294// From implementations for all feature-gated types
295
296// UUID support with SQLx re-exported types
297#[cfg(feature = "uuid")]
298impl From<sqlx_data_integration::Uuid> for FilterValue {
299    fn from(value: sqlx_data_integration::Uuid) -> Self {
300        FilterValue::Uuid(value)
301    }
302}
303
304// Chrono DateTime support with SQLx re-exported types
305#[cfg(feature = "chrono")]
306impl From<sqlx_data_integration::DateTime> for FilterValue {
307    fn from(value: sqlx_data_integration::DateTime) -> Self {
308        FilterValue::DateTimeChrono(value)
309    }
310}
311
312#[cfg(feature = "chrono")]
313impl From<sqlx_data_integration::NaiveDateTime> for FilterValue {
314    fn from(value: sqlx_data_integration::NaiveDateTime) -> Self {
315        FilterValue::NaiveDateTime(value)
316    }
317}
318
319#[cfg(feature = "chrono")]
320impl From<sqlx_data_integration::NaiveDate> for FilterValue {
321    fn from(value: sqlx_data_integration::NaiveDate) -> Self {
322        FilterValue::NaiveDate(value)
323    }
324}
325
326#[cfg(feature = "chrono")]
327impl From<sqlx_data_integration::NaiveTime> for FilterValue {
328    fn from(value: sqlx_data_integration::NaiveTime) -> Self {
329        FilterValue::NaiveTime(value)
330    }
331}
332
333// Time crate support with SQLx re-exported types
334#[cfg(all(feature = "time", not(feature = "chrono")))]
335impl From<sqlx_data_integration::DateTime> for FilterValue {
336    fn from(value: sqlx_data_integration::DateTime) -> Self {
337        FilterValue::OffsetDateTime(value)
338    }
339}
340
341#[cfg(all(feature = "time", not(feature = "chrono")))]
342impl From<sqlx_data_integration::PrimitiveDateTime> for FilterValue {
343    fn from(value: sqlx_data_integration::PrimitiveDateTime) -> Self {
344        FilterValue::PrimitiveDateTime(value)
345    }
346}
347
348#[cfg(all(feature = "time", not(feature = "chrono")))]
349impl From<sqlx_data_integration::Date> for FilterValue {
350    fn from(value: sqlx_data_integration::Date) -> Self {
351        FilterValue::Date(value)
352    }
353}
354
355#[cfg(all(feature = "time", not(feature = "chrono")))]
356impl From<sqlx_data_integration::Time> for FilterValue {
357    fn from(value: sqlx_data_integration::Time) -> Self {
358        FilterValue::Time(value)
359    }
360}
361
362// Decimal support with SQLx re-exported types
363#[cfg(all(feature = "rust_decimal", not(feature = "sqlite")))]
364impl From<sqlx_data_integration::Decimal> for FilterValue {
365    fn from(value: sqlx_data_integration::Decimal) -> Self {
366        FilterValue::RustDecimal(value)
367    }
368}
369
370#[cfg(all(feature = "bigdecimal", not(feature = "sqlite")))]
371impl From<sqlx_data_integration::Decimal> for FilterValue {
372    fn from(value: sqlx_data_integration::Decimal) -> Self {
373        FilterValue::Decimal(value)
374    }
375}
376
377// Network types with SQLx re-exported types
378#[cfg(all(feature = "ipnet", not(feature = "sqlite")))]
379impl From<sqlx_data_integration::IpNet> for FilterValue {
380    fn from(value: sqlx_data_integration::IpNet) -> Self {
381        FilterValue::IpNet(value)
382    }
383}
384
385#[cfg(all(feature = "ipnet", not(feature = "sqlite")))]
386impl From<sqlx_data_integration::Ipv4Net> for FilterValue {
387    fn from(value: sqlx_data_integration::Ipv4Net) -> Self {
388        FilterValue::Ipv4Net(value)
389    }
390}
391
392#[cfg(all(feature = "ipnet", not(feature = "sqlite")))]
393impl From<sqlx_data_integration::Ipv6Net> for FilterValue {
394    fn from(value: sqlx_data_integration::Ipv6Net) -> Self {
395        FilterValue::Ipv6Net(value)
396    }
397}
398
399#[cfg(all(feature = "ipnetwork", not(feature = "sqlite")))]
400impl From<sqlx_data_integration::IpNetwork> for FilterValue {
401    fn from(value: sqlx_data_integration::IpNetwork) -> Self {
402        FilterValue::IpNetwork(value)
403    }
404}
405
406// MAC address with SQLx re-exported type
407#[cfg(feature = "mac_address")]
408impl From<sqlx_data_integration::MacAddress> for FilterValue {
409    fn from(value: sqlx_data_integration::MacAddress) -> Self {
410        FilterValue::MacAddress(value)
411    }
412}
413
414// Binary data types with SQLx re-exported types
415#[cfg(all(feature = "bit-vec", not(feature = "sqlite")))]
416impl From<sqlx_data_integration::BitVec> for FilterValue {
417    fn from(value: sqlx_data_integration::BitVec) -> Self {
418        FilterValue::BitVec(value)
419    }
420}
421
422#[cfg(feature = "bstr")]
423impl From<sqlx_data_integration::BString> for FilterValue {
424    fn from(value: sqlx_data_integration::BString) -> Self {
425        FilterValue::BStr(value)
426    }
427}
428
429#[cfg(test)]
430mod tests {
431    use super::*;
432
433    #[test]
434    fn test_filter_value_from_primitives() {
435        assert_eq!(
436            FilterValue::from("hello"),
437            FilterValue::String(Cow::Owned("hello".to_string()))
438        );
439        assert_eq!(FilterValue::from("hello".to_string()), "hello".into());
440        assert_eq!(FilterValue::from(42i64), FilterValue::Int(42));
441        assert_eq!(FilterValue::from(42u32), FilterValue::UInt(42));
442        assert_eq!(FilterValue::from(1.5f64), FilterValue::Float(1.5f64));
443        assert_eq!(FilterValue::from(true), FilterValue::Bool(true));
444    }
445
446    #[test]
447    fn test_filter_value_from_option() {
448        assert_eq!(FilterValue::from(Some("value")), "value".into());
449        assert_eq!(FilterValue::from(None::<String>), FilterValue::Null);
450        assert_eq!(FilterValue::from(Some(100i32)), FilterValue::Int(100));
451        assert_eq!(FilterValue::from(None::<i32>), FilterValue::Null);
452    }
453
454    #[test]
455    fn test_filter_value_from_vec() {
456        let vec_str: Vec<String> = vec!["admin".to_string(), "user".to_string()];
457        let expected = FilterValue::Array(vec!["admin".into(), "user".into()]);
458        assert_eq!(FilterValue::from(vec_str), expected);
459
460        let vec_int: Vec<i32> = vec![1, 2, 3];
461        let expected_int = FilterValue::Array(vec![
462            FilterValue::Int(1),
463            FilterValue::Int(2),
464            FilterValue::Int(3),
465        ]);
466        assert_eq!(FilterValue::from(vec_int), expected_int);
467    }
468
469    #[test]
470    fn test_filter_value_from_slice() {
471        let value: FilterValue = ["admin", "moderator"].into();
472        let expected = FilterValue::Array(vec!["admin".into(), "moderator".into()]);
473        assert_eq!(value, expected);
474
475        let slice: &[i64] = &[10, 20, 30];
476        let value: FilterValue = slice.into();
477        let expected = FilterValue::Array(vec![
478            FilterValue::Int(10),
479            FilterValue::Int(20),
480            FilterValue::Int(30),
481        ]);
482        assert_eq!(value, expected);
483    }
484
485    #[test]
486    fn test_filter_new_with_vec_and_slice() {
487        let filter1 = Filter::new(
488            "role",
489            FilterOperator::In,
490            vec!["admin".to_string(), "user".to_string()],
491        );
492        assert_eq!(filter1.field, "role");
493        assert_eq!(filter1.operator, FilterOperator::In);
494        assert_eq!(
495            filter1.value,
496            FilterValue::Array(vec!["admin".into(), "user".into(),])
497        );
498        assert!(!filter1.not);
499
500        let filter2 = Filter::new("status", FilterOperator::In, ["active", "pending"]);
501        assert_eq!(filter2.field, "status");
502        assert_eq!(filter2.operator, FilterOperator::In);
503        assert_eq!(
504            filter2.value,
505            FilterValue::Array(vec!["active".into(), "pending".into(),])
506        );
507    }
508
509    #[test]
510    fn test_filter_with_mixed_option_in_array() {
511        let values: Vec<Option<&str>> = vec![Some("yes"), None, Some("maybe")];
512        let filter = Filter::new("answer", FilterOperator::In, values);
513
514        assert_eq!(
515            filter.value,
516            FilterValue::Array(vec!["yes".into(), FilterValue::Null, "maybe".into(),])
517        );
518    }
519
520}