Skip to main content

nodedb_query/scan_filter/
parse.rs

1use super::ScanFilter;
2
3/// Parse simple SQL predicates into `ScanFilter` values.
4///
5/// Handles basic `field op value` predicates joined by AND.
6/// Supports: `=`, `!=`, `<>`, `>`, `>=`, `<`, `<=`, `LIKE`, `ILIKE`.
7/// Values: single-quoted strings, numbers, `TRUE`/`FALSE`, `NULL`.
8///
9/// For complex predicates (OR, subqueries, functions), returns empty vec
10/// (match all — facet counts will be unfiltered).
11pub fn parse_simple_predicates(text: &str) -> Vec<ScanFilter> {
12    let mut filters = Vec::new();
13    for clause in text.split(" AND ").flat_map(|s| s.split(" and ")) {
14        let clause = clause.trim();
15        if clause.is_empty() {
16            continue;
17        }
18        if let Some(f) = parse_single_predicate(clause) {
19            filters.push(f);
20        }
21    }
22    filters
23}
24
25fn parse_single_predicate(clause: &str) -> Option<ScanFilter> {
26    let ops = &[">=", "<=", "!=", "<>", "=", ">", "<"];
27    for op_str in ops {
28        if let Some(pos) = clause.find(op_str) {
29            let field = clause[..pos].trim().to_string();
30            let raw_value = clause[pos + op_str.len()..].trim();
31            let op = match *op_str {
32                "=" => "eq",
33                "!=" | "<>" => "ne",
34                ">" => "gt",
35                ">=" => "gte",
36                "<" => "lt",
37                "<=" => "lte",
38                _ => return None,
39            };
40            return Some(ScanFilter {
41                field,
42                op: super::FilterOp::parse_op(op),
43                value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
44                clauses: Vec::new(),
45                expr: None,
46            });
47        }
48    }
49
50    let upper = clause.to_uppercase();
51    if let Some(pos) = upper.find(" LIKE ") {
52        let field = clause[..pos].trim().to_string();
53        let raw_value = clause[pos + 6..].trim();
54        return Some(ScanFilter {
55            field,
56            op: super::FilterOp::Like,
57            value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
58            clauses: Vec::new(),
59            expr: None,
60        });
61    }
62    if let Some(pos) = upper.find(" ILIKE ") {
63        let field = clause[..pos].trim().to_string();
64        let raw_value = clause[pos + 7..].trim();
65        return Some(ScanFilter {
66            field,
67            op: super::FilterOp::Ilike,
68            value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
69            clauses: Vec::new(),
70            expr: None,
71        });
72    }
73
74    None
75}
76
77fn parse_predicate_value(raw: &str) -> serde_json::Value {
78    let raw = raw.trim();
79    if raw.starts_with('\'') && raw.ends_with('\'') && raw.len() >= 2 {
80        let inner = &raw[1..raw.len() - 1];
81        return serde_json::Value::String(inner.replace("''", "'"));
82    }
83    let upper = raw.to_uppercase();
84    if upper == "TRUE" {
85        return serde_json::Value::Bool(true);
86    }
87    if upper == "FALSE" {
88        return serde_json::Value::Bool(false);
89    }
90    if upper == "NULL" {
91        return serde_json::Value::Null;
92    }
93    if let Ok(i) = raw.parse::<i64>() {
94        return serde_json::json!(i);
95    }
96    if let Ok(f) = raw.parse::<f64>() {
97        return serde_json::json!(f);
98    }
99    serde_json::Value::String(raw.to_string())
100}