Skip to main content

postgrest_parser/parser/
filter.rs

1use super::common::{field, parse_field_fallback};
2use crate::ast::{Field, Filter, FilterOperator, FilterValue, Quantifier};
3use crate::error::ParseError;
4
5type OperatorValueResult = (bool, String, Option<Quantifier>, Option<String>, String);
6
7/// Parses a PostgREST filter from field and value strings.
8///
9/// Supports all 22+ PostgREST filter operators, quantifiers, negation, and full-text search.
10///
11/// # Filter Syntax
12///
13/// - Basic: `field=operator.value`
14/// - Negated: `field=not.operator.value`
15/// - Quantifiers: `field=operator(any).{val1,val2}` or `field=operator(all).{val1,val2}`
16/// - FTS: `field=fts(lang).search terms`
17/// - JSON: `data->key=operator.value` or `data->>key=operator.value`
18///
19/// # Examples
20///
21/// ```
22/// use postgrest_parser::parse_filter;
23///
24/// // Basic comparison
25/// let filter = parse_filter("age", "gte.18").unwrap();
26/// assert!(!filter.negated);
27///
28/// // Negation
29/// let filter = parse_filter("status", "not.eq.deleted").unwrap();
30/// assert!(filter.negated);
31///
32/// // Array containment
33/// let filter = parse_filter("tags", "cs.{rust,postgres}").unwrap();
34///
35/// // Quantifier with array
36/// let filter = parse_filter("tags", "eq(any).{rust,elixir}").unwrap();
37///
38/// // Full-text search
39/// let filter = parse_filter("content", "fts(english).search term").unwrap();
40///
41/// // JSON path
42/// let filter = parse_filter("data->user->name", "eq.Alice").unwrap();
43///
44/// // IS NULL
45/// let filter = parse_filter("deleted_at", "is.null").unwrap();
46/// ```
47///
48/// # Errors
49///
50/// Returns `ParseError` if:
51/// - Operator is invalid or missing
52/// - Field syntax is malformed
53/// - Quantifier is used with incompatible operator
54/// - Value format is invalid for the operator
55pub fn parse_filter(field_str: &str, value_str: &str) -> Result<Filter, ParseError> {
56    let parsed_field = parse_field_string(field_str)?;
57    let (negated, operator_str, quantifier, language, value) = parse_operator_value(value_str)?;
58
59    let operator = parse_operator(&operator_str)?;
60    validate_operator_quantifier(&operator, &quantifier, &language)?;
61    let parsed_value = parse_value(&operator, &quantifier, &value)?;
62
63    Ok(Filter {
64        field: parsed_field,
65        operator,
66        value: parsed_value,
67        quantifier,
68        language,
69        negated,
70    })
71}
72
73pub fn parse_field_string(field_str: &str) -> Result<Field, ParseError> {
74    match field(field_str) {
75        Ok((_, field)) => Ok(field),
76        Err(_) => parse_field_fallback(field_str),
77    }
78}
79
80fn parse_operator_value(value_str: &str) -> Result<OperatorValueResult, ParseError> {
81    let parts: Vec<&str> = value_str.split('.').collect();
82
83    let (negated, rest) = if parts.first() == Some(&"not") {
84        (true, &parts[1..])
85    } else {
86        (false, parts.as_slice())
87    };
88
89    if rest.is_empty() {
90        return Err(ParseError::MissingOperatorOrValue);
91    }
92
93    let operator_part = rest[0];
94
95    let (operator, mut quantifier, mut language) =
96        if operator_part.contains('(') && operator_part.ends_with(')') {
97            let open_paren = operator_part.find('(').unwrap();
98            let op = &operator_part[..open_paren];
99            let quant_or_lang = &operator_part[open_paren + 1..operator_part.len() - 1];
100
101            match quant_or_lang {
102                "any" => (op.to_string(), Some(Quantifier::Any), None),
103                "all" => (op.to_string(), Some(Quantifier::All), None),
104                _ => (op.to_string(), None, Some(quant_or_lang.to_string())),
105            }
106        } else {
107            (operator_part.to_string(), None, None)
108        };
109
110    if rest.len() == 1 {
111        return Ok((negated, operator, quantifier, language, String::new()));
112    }
113
114    let (value_quant, value_lang, value) = extract_quantifier_or_language(&rest[1..])?;
115
116    if quantifier.is_none() {
117        quantifier = value_quant;
118    }
119    if language.is_none() {
120        language = value_lang;
121    }
122
123    Ok((negated, operator, quantifier, language, value))
124}
125
126fn extract_quantifier_or_language(
127    parts: &[&str],
128) -> Result<(Option<Quantifier>, Option<String>, String), ParseError> {
129    let (quantifier, language, value) = match parts {
130        [rest] => {
131            if rest.is_empty() {
132                (None, None, String::new())
133            } else {
134                (None, None, rest.to_string())
135            }
136        }
137        [quant_or_lang, value] => {
138            if quant_or_lang.ends_with(')') {
139                if quant_or_lang.starts_with('(') {
140                    let inner = &quant_or_lang[1..quant_or_lang.len() - 1];
141                    if inner == "any" {
142                        (Some(Quantifier::Any), None, value.to_string())
143                    } else if inner == "all" {
144                        (Some(Quantifier::All), None, value.to_string())
145                    } else {
146                        (None, Some(inner.to_string()), value.to_string())
147                    }
148                } else {
149                    (None, None, format!("{}.{}", quant_or_lang, value))
150                }
151            } else {
152                (None, None, format!("{}.{}", quant_or_lang, value))
153            }
154        }
155        [quantifier, language, value] => {
156            let quant = if quantifier.ends_with(')') {
157                match *quantifier {
158                    "(any)" => Some(Quantifier::Any),
159                    "(all)" => Some(Quantifier::All),
160                    _ => return Err(ParseError::InvalidQuantifier(quantifier.to_string())),
161                }
162            } else {
163                return Err(ParseError::InvalidQuantifier(quantifier.to_string()));
164            };
165
166            if language.ends_with(')') {
167                let inner = &language[1..language.len() - 1];
168                (quant, Some(inner.to_string()), value.to_string())
169            } else {
170                (quant, None, format!("{}.{}", language, value))
171            }
172        }
173        _ => return Err(ParseError::InvalidFilterFormat(format!("{:?}", parts))),
174    };
175
176    Ok((quantifier, language, value))
177}
178
179fn parse_operator(op_str: &str) -> Result<FilterOperator, ParseError> {
180    match op_str.to_lowercase().as_str() {
181        "eq" => Ok(FilterOperator::Eq),
182        "neq" => Ok(FilterOperator::Neq),
183        "gt" => Ok(FilterOperator::Gt),
184        "gte" => Ok(FilterOperator::Gte),
185        "lt" => Ok(FilterOperator::Lt),
186        "lte" => Ok(FilterOperator::Lte),
187        "like" => Ok(FilterOperator::Like),
188        "ilike" => Ok(FilterOperator::Ilike),
189        "match" => Ok(FilterOperator::Match),
190        "imatch" => Ok(FilterOperator::Imatch),
191        "in" => Ok(FilterOperator::In),
192        "is" => Ok(FilterOperator::Is),
193        "fts" => Ok(FilterOperator::Fts),
194        "plfts" => Ok(FilterOperator::Plfts),
195        "phfts" => Ok(FilterOperator::Phfts),
196        "wfts" => Ok(FilterOperator::Wfts),
197        "cs" => Ok(FilterOperator::Cs),
198        "cd" => Ok(FilterOperator::Cd),
199        "ov" => Ok(FilterOperator::Ov),
200        "sl" => Ok(FilterOperator::Sl),
201        "sr" => Ok(FilterOperator::Sr),
202        "nxl" => Ok(FilterOperator::Nxl),
203        "nxr" => Ok(FilterOperator::Nxr),
204        "adj" => Ok(FilterOperator::Adj),
205        _ => Err(ParseError::UnknownOperator(op_str.to_string())),
206    }
207}
208
209fn validate_operator_quantifier(
210    operator: &FilterOperator,
211    quantifier: &Option<Quantifier>,
212    language: &Option<String>,
213) -> Result<(), ParseError> {
214    match (operator, quantifier, language) {
215        (FilterOperator::In, Some(_), _)
216        | (FilterOperator::Cs, Some(_), _)
217        | (FilterOperator::Cd, Some(_), _)
218        | (FilterOperator::Ov, Some(_), _)
219        | (FilterOperator::Sl, Some(_), _)
220        | (FilterOperator::Sr, Some(_), _)
221        | (FilterOperator::Nxl, Some(_), _)
222        | (FilterOperator::Nxr, Some(_), _)
223        | (FilterOperator::Adj, Some(_), _)
224        | (FilterOperator::Is, Some(_), _) => Err(ParseError::QuantifierNotSupported),
225        (
226            FilterOperator::Fts
227            | FilterOperator::Plfts
228            | FilterOperator::Phfts
229            | FilterOperator::Wfts,
230            Some(Quantifier::Any | Quantifier::All),
231            _,
232        ) => Err(ParseError::InvalidFtsLanguage(
233            "any/all not supported for FTS".to_string(),
234        )),
235        _ => Ok(()),
236    }
237}
238
239fn parse_value(
240    operator: &FilterOperator,
241    quantifier: &Option<Quantifier>,
242    value_str: &str,
243) -> Result<FilterValue, ParseError> {
244    match (operator, quantifier) {
245        (FilterOperator::In, _) => parse_list_value(value_str, '(', ')'),
246        (FilterOperator::Cs | FilterOperator::Cd, _) => {
247            Ok(FilterValue::Single(value_str.to_string()))
248        }
249        (FilterOperator::Ov, _) => parse_list_value(value_str, '(', ')'),
250        (
251            FilterOperator::Eq
252            | FilterOperator::Neq
253            | FilterOperator::Gt
254            | FilterOperator::Gte
255            | FilterOperator::Lt
256            | FilterOperator::Lte
257            | FilterOperator::Like
258            | FilterOperator::Ilike
259            | FilterOperator::Match
260            | FilterOperator::Imatch,
261            Some(Quantifier::Any | Quantifier::All),
262        ) => parse_list_value(value_str, '{', '}'),
263        _ => Ok(FilterValue::Single(value_str.to_string())),
264    }
265}
266
267fn parse_list_value(value_str: &str, open: char, close: char) -> Result<FilterValue, ParseError> {
268    if value_str.starts_with(open) && value_str.ends_with(close) {
269        let inner = &value_str[1..value_str.len() - 1];
270        let items: Vec<String> = inner.split(',').map(|s| s.trim().to_string()).collect();
271        Ok(FilterValue::List(items))
272    } else {
273        Err(ParseError::ExpectedListFormat(format!(
274            "expected list with {} and {}",
275            open, close
276        )))
277    }
278}
279
280pub fn reserved_key(key: &str) -> bool {
281    matches!(
282        key,
283        "select" | "order" | "limit" | "offset" | "on_conflict" | "columns" | "returning"
284    )
285}
286
287#[cfg(test)]
288mod tests {
289    use super::*;
290
291    #[test]
292    fn test_parse_filter_eq() {
293        let result = parse_filter("id", "eq.1");
294        assert!(result.is_ok());
295        let filter = result.unwrap();
296        assert_eq!(filter.operator, FilterOperator::Eq);
297        assert_eq!(filter.value, FilterValue::Single("1".to_string()));
298        assert!(!filter.negated);
299    }
300
301    #[test]
302    fn test_parse_filter_negated() {
303        let result = parse_filter("status", "not.eq.active");
304        assert!(result.is_ok());
305        let filter = result.unwrap();
306        assert!(filter.negated);
307    }
308
309    #[test]
310    fn test_parse_filter_in_operator() {
311        let result = parse_filter("status", "in.(active,pending)");
312        assert!(result.is_ok());
313        let filter = result.unwrap();
314        assert_eq!(filter.operator, FilterOperator::In);
315        match filter.value {
316            FilterValue::List(items) => {
317                assert_eq!(items.len(), 2);
318                assert!(items.contains(&"active".to_string()));
319            }
320            _ => panic!("Expected list value"),
321        }
322    }
323
324    #[test]
325    fn test_parse_filter_with_quantifier() {
326        let result = parse_filter("status", "eq(any).{active,pending}");
327        assert!(result.is_ok());
328        let filter = result.unwrap();
329        assert_eq!(filter.quantifier, Some(Quantifier::Any));
330        assert!(matches!(filter.value, FilterValue::List(_)));
331    }
332
333    #[test]
334    fn test_parse_filter_with_json_path() {
335        let result = parse_filter("data->name", "eq.test");
336        assert!(result.is_ok());
337        let filter = result.unwrap();
338        assert_eq!(filter.field.name, "data");
339        assert_eq!(filter.field.json_path.len(), 1);
340    }
341
342    #[test]
343    fn test_parse_filter_with_type_cast() {
344        let result = parse_filter("price", "eq.100");
345        assert!(result.is_ok());
346        assert!(result.is_ok());
347    }
348
349    #[test]
350    fn test_parse_filter_fts_with_language() {
351        let result = parse_filter("content", "fts(english).search");
352        assert!(result.is_ok());
353        let filter = result.unwrap();
354        assert_eq!(filter.language, Some("english".to_string()));
355    }
356
357    #[test]
358    fn test_parse_filter_unknown_operator() {
359        let result = parse_filter("id", "invalid.1");
360        assert!(matches!(result, Err(ParseError::UnknownOperator(_))));
361    }
362
363    #[test]
364    fn test_parse_filter_invalid_quantifier() {
365        let result = parse_filter("status", "is(any).null");
366        assert!(matches!(result, Err(ParseError::QuantifierNotSupported)));
367    }
368
369    #[test]
370    fn test_parse_filter_unclosed_parenthesis() {
371        let result = parse_filter("status", "in.(active");
372        assert!(matches!(result, Err(ParseError::ExpectedListFormat(_))));
373    }
374
375    #[test]
376    fn test_reserved_key() {
377        assert!(reserved_key("select"));
378        assert!(reserved_key("order"));
379        assert!(reserved_key("limit"));
380        assert!(!reserved_key("id"));
381    }
382
383    #[test]
384    fn test_parse_filter_with_whitespace_in_list() {
385        let result = parse_filter("status", "in.(active, pending, closed)");
386        assert!(result.is_ok());
387        let filter = result.unwrap();
388        assert_eq!(filter.operator, FilterOperator::In);
389        match filter.value {
390            FilterValue::List(items) => {
391                assert_eq!(items.len(), 3);
392                assert!(items.contains(&"active".to_string()));
393                assert!(items.contains(&"pending".to_string()));
394                assert!(items.contains(&"closed".to_string()));
395            }
396            _ => panic!("Expected list value"),
397        }
398    }
399
400    #[test]
401    fn test_parse_filter_comparison_operators() {
402        // GT operator
403        let result = parse_filter("age", "gt.18");
404        assert!(result.is_ok());
405        let filter = result.unwrap();
406        assert_eq!(filter.operator, FilterOperator::Gt);
407        assert_eq!(filter.value, FilterValue::Single("18".to_string()));
408
409        // GTE operator
410        let result = parse_filter("age", "gte.21");
411        assert!(result.is_ok());
412        assert_eq!(result.unwrap().operator, FilterOperator::Gte);
413
414        // LT operator
415        let result = parse_filter("age", "lt.65");
416        assert!(result.is_ok());
417        assert_eq!(result.unwrap().operator, FilterOperator::Lt);
418
419        // LTE operator
420        let result = parse_filter("age", "lte.65");
421        assert!(result.is_ok());
422        assert_eq!(result.unwrap().operator, FilterOperator::Lte);
423
424        // NEQ operator
425        let result = parse_filter("status", "neq.inactive");
426        assert!(result.is_ok());
427        assert_eq!(result.unwrap().operator, FilterOperator::Neq);
428    }
429
430    #[test]
431    fn test_parse_filter_match_operators() {
432        // Match operator
433        let result = parse_filter("name", "match.^John");
434        assert!(result.is_ok());
435        let filter = result.unwrap();
436        assert_eq!(filter.operator, FilterOperator::Match);
437
438        // Imatch operator
439        let result = parse_filter("name", "imatch.^john");
440        assert!(result.is_ok());
441        assert_eq!(result.unwrap().operator, FilterOperator::Imatch);
442    }
443
444    #[test]
445    fn test_parse_filter_array_operators() {
446        // CS operator (contains)
447        let result = parse_filter("tags", "cs.{rust}");
448        assert!(result.is_ok());
449        assert_eq!(result.unwrap().operator, FilterOperator::Cs);
450
451        // CD operator (contained in)
452        let result = parse_filter("tags", "cd.{rust,elixir}");
453        assert!(result.is_ok());
454        assert_eq!(result.unwrap().operator, FilterOperator::Cd);
455
456        // OV operator (overlaps)
457        let result = parse_filter("tags", "ov.(rust,elixir)");
458        assert!(result.is_ok());
459        assert_eq!(result.unwrap().operator, FilterOperator::Ov);
460    }
461
462    #[test]
463    fn test_parse_filter_range_operators() {
464        // SL operator (strictly left)
465        let result = parse_filter("range", "sl.[1,10)");
466        assert!(result.is_ok());
467        assert_eq!(result.unwrap().operator, FilterOperator::Sl);
468
469        // SR operator (strictly right)
470        let result = parse_filter("range", "sr.[1,10)");
471        assert!(result.is_ok());
472        assert_eq!(result.unwrap().operator, FilterOperator::Sr);
473
474        // NXL operator
475        let result = parse_filter("range", "nxl.[1,10)");
476        assert!(result.is_ok());
477        assert_eq!(result.unwrap().operator, FilterOperator::Nxl);
478
479        // NXR operator
480        let result = parse_filter("range", "nxr.[1,10)");
481        assert!(result.is_ok());
482        assert_eq!(result.unwrap().operator, FilterOperator::Nxr);
483
484        // ADJ operator (adjacent)
485        let result = parse_filter("range", "adj.[1,10)");
486        assert!(result.is_ok());
487        assert_eq!(result.unwrap().operator, FilterOperator::Adj);
488    }
489
490    #[test]
491    fn test_parse_filter_fts_operators() {
492        // PLFTS operator
493        let result = parse_filter("content", "plfts.search");
494        assert!(result.is_ok());
495        assert_eq!(result.unwrap().operator, FilterOperator::Plfts);
496
497        // PHFTS operator
498        let result = parse_filter("content", "phfts.search phrase");
499        assert!(result.is_ok());
500        assert_eq!(result.unwrap().operator, FilterOperator::Phfts);
501
502        // WFTS operator
503        let result = parse_filter("content", "wfts.search query");
504        assert!(result.is_ok());
505        assert_eq!(result.unwrap().operator, FilterOperator::Wfts);
506    }
507
508    #[test]
509    fn test_parse_filter_negated_operators() {
510        // Negated GT
511        let result = parse_filter("age", "not.gt.18");
512        assert!(result.is_ok());
513        let filter = result.unwrap();
514        assert!(filter.negated);
515        assert_eq!(filter.operator, FilterOperator::Gt);
516
517        // Negated IN
518        let result = parse_filter("status", "not.in.(active,pending)");
519        assert!(result.is_ok());
520        let filter = result.unwrap();
521        assert!(filter.negated);
522        assert_eq!(filter.operator, FilterOperator::In);
523
524        // Negated LIKE
525        let result = parse_filter("name", "not.like.*John*");
526        assert!(result.is_ok());
527        let filter = result.unwrap();
528        assert!(filter.negated);
529        assert_eq!(filter.operator, FilterOperator::Like);
530    }
531}