sqlx_utils/traits/
sql_filter.rs

1//! Sql filtering trait for defining type safe dynamic filters.
2
3use crate::types::Database;
4use sqlx::{Database as DatabaseTrait, QueryBuilder};
5
6/// Trait for creating SQL filter conditions that can be applied to database queries.
7///
8/// The `SqlFilter` trait provides a standardized way to define reusable, type-safe
9/// SQL filter conditions. Filters can be composed and combined using logical operators
10/// to create complex query criteria.
11///
12/// # Type Parameters
13///
14/// * `'args`: The lifetime for query arguments
15/// * `DB`: The database backend type (defaults to the configured default database)
16///
17/// # Examples
18///
19/// Basic implementation using the macro:
20/// ```rust
21/// # use sqlx_utils::sql_filter;
22/// # use sqlx_utils::traits::SqlFilter;
23/// # use sqlx_utils::types::Database;
24/// # use sqlx::QueryBuilder;
25///
26/// sql_filter! {
27///     pub struct UserFilter {
28///         SELECT * FROM users WHERE
29///         id = i32 AND
30///         ?name LIKE String AND
31///         ?email LIKE String
32///     }
33/// }
34///
35/// // Usage
36/// # fn example() {
37/// let filter = UserFilter::new(42)
38///     .name("Alice%")
39///     .email("alice@example.com");
40///
41/// let mut builder = QueryBuilder::<Database>::new("SELECT * FROM users WHERE ");
42/// filter.apply_filter(&mut builder);
43/// # }
44/// ```
45///
46/// Custom implementation:
47/// ```rust
48/// # use sqlx_utils::traits::SqlFilter;
49/// # use sqlx_utils::types::Database;
50/// # use sqlx::QueryBuilder;
51///
52/// struct AgeRangeFilter {
53///     min_age: Option<i32>,
54///     max_age: Option<i32>,
55/// }
56///
57/// impl<'args> SqlFilter<'args, Database> for AgeRangeFilter {
58///     fn apply_filter(self, builder: &mut QueryBuilder<'args, Database>) {
59///         if self.min_age.is_some() || self.max_age.is_some() {
60///             let mut first = true;
61///
62///             if let Some(min) = self.min_age {
63///                 if !first { builder.push(" AND "); }
64///                 builder.push("age >= ");
65///                 builder.push_bind(min);
66///                 first = false;
67///             }
68///
69///             if let Some(max) = self.max_age {
70///                 if !first { builder.push(" AND "); }
71///                 builder.push("age <= ");
72///                 builder.push_bind(max);
73///             }
74///         }
75///     }
76///
77///     fn should_apply_filter(&self) -> bool {
78///         self.min_age.is_some() || self.max_age.is_some()
79///     }
80/// }
81/// ```
82///
83/// # Implementation Notes
84///
85/// When implementing this trait:
86///
87/// 1. The [`apply_filter`](SqlFilter::apply_filter) method should add SQL conditions to the builder
88/// 2. The [`should_apply_filter`](SqlFilter::should_apply_filter) method should return `true` if this filter has criteria to apply
89/// 3. Consider using the [`sql_filter!`](crate::sql_filter) macro for common filter patterns
90/// 4. Ensure proper parameterization to prevent SQL injection
91#[diagnostic::on_unimplemented(
92    message = "The filter type `{Self}` must implement `SqlFilter<'args>` to be used in queries",
93    label = "this type does not implement `SqlFilter<'args>`",
94    note = "Type `{Self}` does not implement the `SqlFilter` trait with the required lifetime `'args`.",
95    note = "SqlFilter is implemented by default for the `sqlx_utils::types::Database` database, things might not work if you are using a custom database."
96)]
97pub trait SqlFilter<'args, DB: DatabaseTrait = Database> {
98    /// Applies this filter's conditions to a SQL query builder.
99    ///
100    /// This method should add the necessary SQL conditions represented by this filter
101    /// to the provided query builder. It should handle binding parameters securely.
102    ///
103    /// # Parameters
104    ///
105    /// * `self` - The filter instance, consumed during application
106    /// * `builder` - The query builder to which the filter will be applied
107    fn apply_filter(self, builder: &mut QueryBuilder<'args, DB>);
108
109    /// Determines whether this filter has meaningful conditions to apply.
110    ///
111    /// This method should return `true` if the filter has non-default conditions
112    /// that should be included in a query, or `false` if the filter is empty or
113    /// represents default criteria that don't need to be applied.
114    ///
115    /// # Returns
116    ///
117    /// * `true` - If the filter has conditions to apply
118    /// * `false` - If the filter has no conditions to apply
119    fn should_apply_filter(&self) -> bool;
120}