Skip to main content

nodedb_query/scan_filter/
parse.rs

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