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            });
46        }
47    }
48
49    let upper = clause.to_uppercase();
50    if let Some(pos) = upper.find(" LIKE ") {
51        let field = clause[..pos].trim().to_string();
52        let raw_value = clause[pos + 6..].trim();
53        return Some(ScanFilter {
54            field,
55            op: super::FilterOp::Like,
56            value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
57            clauses: Vec::new(),
58        });
59    }
60    if let Some(pos) = upper.find(" ILIKE ") {
61        let field = clause[..pos].trim().to_string();
62        let raw_value = clause[pos + 7..].trim();
63        return Some(ScanFilter {
64            field,
65            op: super::FilterOp::Ilike,
66            value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
67            clauses: Vec::new(),
68        });
69    }
70
71    None
72}
73
74fn parse_predicate_value(raw: &str) -> serde_json::Value {
75    let raw = raw.trim();
76    if raw.starts_with('\'') && raw.ends_with('\'') && raw.len() >= 2 {
77        let inner = &raw[1..raw.len() - 1];
78        return serde_json::Value::String(inner.replace("''", "'"));
79    }
80    let upper = raw.to_uppercase();
81    if upper == "TRUE" {
82        return serde_json::Value::Bool(true);
83    }
84    if upper == "FALSE" {
85        return serde_json::Value::Bool(false);
86    }
87    if upper == "NULL" {
88        return serde_json::Value::Null;
89    }
90    if let Ok(i) = raw.parse::<i64>() {
91        return serde_json::json!(i);
92    }
93    if let Ok(f) = raw.parse::<f64>() {
94        return serde_json::json!(f);
95    }
96    serde_json::Value::String(raw.to_string())
97}