Skip to main content

supabase_client_query/
filter.rs

1use crate::sql::{
2    ArrayRangeOperator, FilterCondition, FilterOperator, IntoSqlParam, IsValue, ParamStore,
3    PatternOperator, TextSearchType, validate_column_name,
4};
5/// Trait providing all filter methods for query builders.
6///
7/// Implementors must provide access to the internal filter list and param store.
8pub trait Filterable: Sized {
9    /// Get a mutable reference to the filter list.
10    fn filters_mut(&mut self) -> &mut Vec<FilterCondition>;
11    /// Get a mutable reference to the parameter store.
12    fn params_mut(&mut self) -> &mut ParamStore;
13
14    /// Filter: column = value
15    fn eq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
16        if let Err(e) = validate_column_name(column) {
17            tracing::error!("Invalid column name in eq filter: {e}");
18            return self;
19        }
20        let idx = self.params_mut().push_value(value);
21        self.filters_mut().push(FilterCondition::Comparison {
22            column: column.to_string(),
23            operator: FilterOperator::Eq,
24            param_index: idx,
25        });
26        self
27    }
28
29    /// Filter: column != value
30    fn neq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
31        if let Err(e) = validate_column_name(column) {
32            tracing::error!("Invalid column name in neq filter: {e}");
33            return self;
34        }
35        let idx = self.params_mut().push_value(value);
36        self.filters_mut().push(FilterCondition::Comparison {
37            column: column.to_string(),
38            operator: FilterOperator::Neq,
39            param_index: idx,
40        });
41        self
42    }
43
44    /// Filter: column > value
45    fn gt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
46        if let Err(e) = validate_column_name(column) {
47            tracing::error!("Invalid column name in gt filter: {e}");
48            return self;
49        }
50        let idx = self.params_mut().push_value(value);
51        self.filters_mut().push(FilterCondition::Comparison {
52            column: column.to_string(),
53            operator: FilterOperator::Gt,
54            param_index: idx,
55        });
56        self
57    }
58
59    /// Filter: column >= value
60    fn gte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
61        if let Err(e) = validate_column_name(column) {
62            tracing::error!("Invalid column name in gte filter: {e}");
63            return self;
64        }
65        let idx = self.params_mut().push_value(value);
66        self.filters_mut().push(FilterCondition::Comparison {
67            column: column.to_string(),
68            operator: FilterOperator::Gte,
69            param_index: idx,
70        });
71        self
72    }
73
74    /// Filter: column < value
75    fn lt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
76        if let Err(e) = validate_column_name(column) {
77            tracing::error!("Invalid column name in lt filter: {e}");
78            return self;
79        }
80        let idx = self.params_mut().push_value(value);
81        self.filters_mut().push(FilterCondition::Comparison {
82            column: column.to_string(),
83            operator: FilterOperator::Lt,
84            param_index: idx,
85        });
86        self
87    }
88
89    /// Filter: column <= value
90    fn lte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
91        if let Err(e) = validate_column_name(column) {
92            tracing::error!("Invalid column name in lte filter: {e}");
93            return self;
94        }
95        let idx = self.params_mut().push_value(value);
96        self.filters_mut().push(FilterCondition::Comparison {
97            column: column.to_string(),
98            operator: FilterOperator::Lte,
99            param_index: idx,
100        });
101        self
102    }
103
104    /// Filter: column LIKE pattern
105    fn like(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
106        if let Err(e) = validate_column_name(column) {
107            tracing::error!("Invalid column name in like filter: {e}");
108            return self;
109        }
110        let idx = self.params_mut().push_value(pattern);
111        self.filters_mut().push(FilterCondition::Pattern {
112            column: column.to_string(),
113            operator: PatternOperator::Like,
114            param_index: idx,
115        });
116        self
117    }
118
119    /// Filter: column ILIKE pattern (case-insensitive)
120    fn ilike(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
121        if let Err(e) = validate_column_name(column) {
122            tracing::error!("Invalid column name in ilike filter: {e}");
123            return self;
124        }
125        let idx = self.params_mut().push_value(pattern);
126        self.filters_mut().push(FilterCondition::Pattern {
127            column: column.to_string(),
128            operator: PatternOperator::ILike,
129            param_index: idx,
130        });
131        self
132    }
133
134    /// Filter: column IS NULL / IS NOT NULL / IS TRUE / IS FALSE
135    fn is(mut self, column: &str, value: IsValue) -> Self {
136        if let Err(e) = validate_column_name(column) {
137            tracing::error!("Invalid column name in is filter: {e}");
138            return self;
139        }
140        self.filters_mut().push(FilterCondition::Is {
141            column: column.to_string(),
142            value,
143        });
144        self
145    }
146
147    /// Filter: column IN (val1, val2, ...)
148    fn in_<V: IntoSqlParam>(mut self, column: &str, values: Vec<V>) -> Self {
149        if let Err(e) = validate_column_name(column) {
150            tracing::error!("Invalid column name in in_ filter: {e}");
151            return self;
152        }
153        let indices: Vec<usize> = values
154            .into_iter()
155            .map(|v| self.params_mut().push_value(v))
156            .collect();
157        self.filters_mut().push(FilterCondition::In {
158            column: column.to_string(),
159            param_indices: indices,
160        });
161        self
162    }
163
164    /// Filter: column @> value (contains)
165    fn contains(mut self, column: &str, value: impl IntoSqlParam) -> Self {
166        if let Err(e) = validate_column_name(column) {
167            tracing::error!("Invalid column name in contains filter: {e}");
168            return self;
169        }
170        let idx = self.params_mut().push_value(value);
171        self.filters_mut().push(FilterCondition::ArrayRange {
172            column: column.to_string(),
173            operator: ArrayRangeOperator::Contains,
174            param_index: idx,
175        });
176        self
177    }
178
179    /// Filter: column <@ value (contained by)
180    fn contained_by(mut self, column: &str, value: impl IntoSqlParam) -> Self {
181        if let Err(e) = validate_column_name(column) {
182            tracing::error!("Invalid column name in contained_by filter: {e}");
183            return self;
184        }
185        let idx = self.params_mut().push_value(value);
186        self.filters_mut().push(FilterCondition::ArrayRange {
187            column: column.to_string(),
188            operator: ArrayRangeOperator::ContainedBy,
189            param_index: idx,
190        });
191        self
192    }
193
194    /// Filter: column && value (overlaps)
195    fn overlaps(mut self, column: &str, value: impl IntoSqlParam) -> Self {
196        if let Err(e) = validate_column_name(column) {
197            tracing::error!("Invalid column name in overlaps filter: {e}");
198            return self;
199        }
200        let idx = self.params_mut().push_value(value);
201        self.filters_mut().push(FilterCondition::ArrayRange {
202            column: column.to_string(),
203            operator: ArrayRangeOperator::Overlaps,
204            param_index: idx,
205        });
206        self
207    }
208
209    /// Filter: column >> value (range strictly greater than)
210    fn range_gt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
211        if let Err(e) = validate_column_name(column) {
212            tracing::error!("Invalid column name in range_gt filter: {e}");
213            return self;
214        }
215        let idx = self.params_mut().push_value(value);
216        self.filters_mut().push(FilterCondition::ArrayRange {
217            column: column.to_string(),
218            operator: ArrayRangeOperator::RangeGt,
219            param_index: idx,
220        });
221        self
222    }
223
224    /// Filter: column &> value (range greater than or equal)
225    fn range_gte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
226        if let Err(e) = validate_column_name(column) {
227            tracing::error!("Invalid column name in range_gte filter: {e}");
228            return self;
229        }
230        let idx = self.params_mut().push_value(value);
231        self.filters_mut().push(FilterCondition::ArrayRange {
232            column: column.to_string(),
233            operator: ArrayRangeOperator::RangeGte,
234            param_index: idx,
235        });
236        self
237    }
238
239    /// Filter: column << value (range strictly less than)
240    fn range_lt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
241        if let Err(e) = validate_column_name(column) {
242            tracing::error!("Invalid column name in range_lt filter: {e}");
243            return self;
244        }
245        let idx = self.params_mut().push_value(value);
246        self.filters_mut().push(FilterCondition::ArrayRange {
247            column: column.to_string(),
248            operator: ArrayRangeOperator::RangeLt,
249            param_index: idx,
250        });
251        self
252    }
253
254    /// Filter: column &< value (range less than or equal)
255    fn range_lte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
256        if let Err(e) = validate_column_name(column) {
257            tracing::error!("Invalid column name in range_lte filter: {e}");
258            return self;
259        }
260        let idx = self.params_mut().push_value(value);
261        self.filters_mut().push(FilterCondition::ArrayRange {
262            column: column.to_string(),
263            operator: ArrayRangeOperator::RangeLte,
264            param_index: idx,
265        });
266        self
267    }
268
269    /// Filter: column -|- value (range adjacent)
270    fn range_adjacent(mut self, column: &str, value: impl IntoSqlParam) -> Self {
271        if let Err(e) = validate_column_name(column) {
272            tracing::error!("Invalid column name in range_adjacent filter: {e}");
273            return self;
274        }
275        let idx = self.params_mut().push_value(value);
276        self.filters_mut().push(FilterCondition::ArrayRange {
277            column: column.to_string(),
278            operator: ArrayRangeOperator::RangeAdjacent,
279            param_index: idx,
280        });
281        self
282    }
283
284    /// Full-text search filter.
285    fn text_search(
286        mut self,
287        column: &str,
288        query: impl IntoSqlParam,
289        search_type: TextSearchType,
290        config: Option<&str>,
291    ) -> Self {
292        if let Err(e) = validate_column_name(column) {
293            tracing::error!("Invalid column name in text_search filter: {e}");
294            return self;
295        }
296        let idx = self.params_mut().push_value(query);
297        self.filters_mut().push(FilterCondition::TextSearch {
298            column: column.to_string(),
299            query_param_index: idx,
300            config: config.map(|s| s.to_string()),
301            search_type,
302        });
303        self
304    }
305
306    /// Negate a filter condition using a closure.
307    fn not(mut self, f: impl FnOnce(FilterCollector) -> FilterCollector) -> Self {
308        let collector = f(FilterCollector::new(self.params_mut()));
309        if let Some(condition) = collector.into_single_condition() {
310            self.filters_mut().push(FilterCondition::Not(Box::new(condition)));
311        }
312        self
313    }
314
315    /// OR filter: combine multiple conditions with OR.
316    fn or_filter(mut self, f: impl FnOnce(FilterCollector) -> FilterCollector) -> Self {
317        let collector = f(FilterCollector::new(self.params_mut()));
318        let conditions = collector.into_conditions();
319        if !conditions.is_empty() {
320            self.filters_mut().push(FilterCondition::Or(conditions));
321        }
322        self
323    }
324
325    /// Match multiple column=value pairs (all must match).
326    fn match_filter(mut self, pairs: Vec<(&str, impl IntoSqlParam + Clone)>) -> Self {
327        let conditions: Vec<(String, usize)> = pairs
328            .into_iter()
329            .filter_map(|(col, val)| {
330                if let Err(e) = validate_column_name(col) {
331                    tracing::error!("Invalid column name in match_filter: {e}");
332                    return None;
333                }
334                let idx = self.params_mut().push_value(val);
335                Some((col.to_string(), idx))
336            })
337            .collect();
338        if !conditions.is_empty() {
339            self.filters_mut().push(FilterCondition::Match { conditions });
340        }
341        self
342    }
343
344    /// Raw filter escape hatch. The string should be a valid SQL boolean expression.
345    fn filter(mut self, raw_sql: &str) -> Self {
346        self.filters_mut()
347            .push(FilterCondition::Raw(raw_sql.to_string()));
348        self
349    }
350}
351
352/// Temporary collector used in closures for `not()` and `or_filter()`.
353pub struct FilterCollector<'a> {
354    filters: Vec<FilterCondition>,
355    params: &'a mut ParamStore,
356}
357
358impl<'a> FilterCollector<'a> {
359    pub fn new(params: &'a mut ParamStore) -> Self {
360        Self {
361            filters: Vec::new(),
362            params,
363        }
364    }
365
366    pub fn eq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
367        if validate_column_name(column).is_ok() {
368            let idx = self.params.push_value(value);
369            self.filters.push(FilterCondition::Comparison {
370                column: column.to_string(),
371                operator: FilterOperator::Eq,
372                param_index: idx,
373            });
374        }
375        self
376    }
377
378    pub fn neq(mut self, column: &str, value: impl IntoSqlParam) -> Self {
379        if validate_column_name(column).is_ok() {
380            let idx = self.params.push_value(value);
381            self.filters.push(FilterCondition::Comparison {
382                column: column.to_string(),
383                operator: FilterOperator::Neq,
384                param_index: idx,
385            });
386        }
387        self
388    }
389
390    pub fn gt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
391        if validate_column_name(column).is_ok() {
392            let idx = self.params.push_value(value);
393            self.filters.push(FilterCondition::Comparison {
394                column: column.to_string(),
395                operator: FilterOperator::Gt,
396                param_index: idx,
397            });
398        }
399        self
400    }
401
402    pub fn gte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
403        if validate_column_name(column).is_ok() {
404            let idx = self.params.push_value(value);
405            self.filters.push(FilterCondition::Comparison {
406                column: column.to_string(),
407                operator: FilterOperator::Gte,
408                param_index: idx,
409            });
410        }
411        self
412    }
413
414    pub fn lt(mut self, column: &str, value: impl IntoSqlParam) -> Self {
415        if validate_column_name(column).is_ok() {
416            let idx = self.params.push_value(value);
417            self.filters.push(FilterCondition::Comparison {
418                column: column.to_string(),
419                operator: FilterOperator::Lt,
420                param_index: idx,
421            });
422        }
423        self
424    }
425
426    pub fn lte(mut self, column: &str, value: impl IntoSqlParam) -> Self {
427        if validate_column_name(column).is_ok() {
428            let idx = self.params.push_value(value);
429            self.filters.push(FilterCondition::Comparison {
430                column: column.to_string(),
431                operator: FilterOperator::Lte,
432                param_index: idx,
433            });
434        }
435        self
436    }
437
438    pub fn like(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
439        if validate_column_name(column).is_ok() {
440            let idx = self.params.push_value(pattern);
441            self.filters.push(FilterCondition::Pattern {
442                column: column.to_string(),
443                operator: PatternOperator::Like,
444                param_index: idx,
445            });
446        }
447        self
448    }
449
450    pub fn ilike(mut self, column: &str, pattern: impl IntoSqlParam) -> Self {
451        if validate_column_name(column).is_ok() {
452            let idx = self.params.push_value(pattern);
453            self.filters.push(FilterCondition::Pattern {
454                column: column.to_string(),
455                operator: PatternOperator::ILike,
456                param_index: idx,
457            });
458        }
459        self
460    }
461
462    pub fn is(mut self, column: &str, value: IsValue) -> Self {
463        if validate_column_name(column).is_ok() {
464            self.filters.push(FilterCondition::Is {
465                column: column.to_string(),
466                value,
467            });
468        }
469        self
470    }
471
472    pub fn into_conditions(self) -> Vec<FilterCondition> {
473        self.filters
474    }
475
476    pub fn into_single_condition(self) -> Option<FilterCondition> {
477        let mut filters = self.filters;
478        if filters.len() == 1 {
479            Some(filters.remove(0))
480        } else if filters.is_empty() {
481            None
482        } else {
483            Some(FilterCondition::And(filters))
484        }
485    }
486}