nodedb_query/scan_filter/
parse.rs1use nodedb_types::find_ascii_case_insensitive;
4
5use super::{FilterOp, ScanFilter};
6
7pub fn parse_simple_predicates(text: &str) -> Vec<ScanFilter> {
16 let mut filters = Vec::new();
17 for clause in text.split(" AND ").flat_map(|s| s.split(" and ")) {
18 let clause = clause.trim();
19 if clause.is_empty() {
20 continue;
21 }
22 if let Some(f) = parse_single_predicate(clause) {
23 filters.push(f);
24 }
25 }
26 filters
27}
28
29fn parse_single_predicate(clause: &str) -> Option<ScanFilter> {
30 let ops = &[">=", "<=", "!=", "<>", "=", ">", "<"];
31 for op_str in ops {
32 if let Some(pos) = clause.find(op_str) {
33 let field = clause[..pos].trim().to_string();
34 let raw_value = clause[pos + op_str.len()..].trim();
35 let op = match *op_str {
36 "=" => FilterOp::Eq,
37 "!=" | "<>" => FilterOp::Ne,
38 ">" => FilterOp::Gt,
39 ">=" => FilterOp::Gte,
40 "<" => FilterOp::Lt,
41 "<=" => FilterOp::Lte,
42 _ => return None,
43 };
44 return Some(ScanFilter {
45 field,
46 op,
47 value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
48 clauses: Vec::new(),
49 expr: None,
50 });
51 }
52 }
53
54 if let Some(pos) = find_ascii_case_insensitive(clause, " LIKE ") {
55 let field = clause[..pos].trim().to_string();
56 let raw_value = clause[pos + 6..].trim();
57 return Some(ScanFilter {
58 field,
59 op: FilterOp::Like,
60 value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
61 clauses: Vec::new(),
62 expr: None,
63 });
64 }
65 if let Some(pos) = find_ascii_case_insensitive(clause, " ILIKE ") {
66 let field = clause[..pos].trim().to_string();
67 let raw_value = clause[pos + 7..].trim();
68 return Some(ScanFilter {
69 field,
70 op: FilterOp::Ilike,
71 value: nodedb_types::Value::from(parse_predicate_value(raw_value)),
72 clauses: Vec::new(),
73 expr: None,
74 });
75 }
76
77 None
78}
79
80fn parse_predicate_value(raw: &str) -> serde_json::Value {
81 let raw = raw.trim();
82 if let Some(inner) = raw
83 .strip_prefix('\'')
84 .and_then(|value| value.strip_suffix('\''))
85 {
86 return serde_json::Value::String(inner.replace("''", "'"));
87 }
88 if raw.eq_ignore_ascii_case("TRUE") {
89 return serde_json::Value::Bool(true);
90 }
91 if raw.eq_ignore_ascii_case("FALSE") {
92 return serde_json::Value::Bool(false);
93 }
94 if raw.eq_ignore_ascii_case("NULL") {
95 return serde_json::Value::Null;
96 }
97 if let Ok(i) = raw.parse::<i64>() {
98 return serde_json::json!(i);
99 }
100 if let Ok(f) = raw.parse::<f64>() {
101 return serde_json::json!(f);
102 }
103 serde_json::Value::String(raw.to_string())
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 #[test]
111 fn unicode_before_like_preserves_original_byte_offsets() {
112 let filters = parse_simple_predicates("Straße LIKE 'ß%'");
113 assert_eq!(filters.len(), 1);
114 assert_eq!(filters[0].field, "Straße");
115 assert_eq!(filters[0].value, nodedb_types::Value::String("ß%".into()));
116
117 let filters = parse_simple_predicates("İstanbul ILIKE 'İ%'");
118 assert_eq!(filters.len(), 1);
119 assert_eq!(filters[0].field, "İstanbul");
120 assert_eq!(filters[0].value, nodedb_types::Value::String("İ%".into()));
121 }
122}