1use crate::CanonicalValue;
2use std::error::Error;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Op {
7 Eq,
8 Ne,
9 Lt,
10 Le,
11 Gt,
12 Ge,
13}
14
15#[derive(Debug, Clone)]
40pub struct Filter {
41 clauses: Vec<Clause>,
42}
43
44#[derive(Debug, Clone)]
45struct Clause {
46 field: String,
47 op: Op,
48 literal: Literal,
49}
50
51#[derive(Debug, Clone)]
52enum Literal {
53 Null,
54 Bool(bool),
55 Number(f64),
56 String(String),
57}
58
59impl Filter {
60 pub fn parse(input: &str) -> Result<Self, Box<dyn Error>> {
65 let mut clauses = Vec::new();
66 for raw in input.split(" and ") {
67 let part = raw.trim();
68 if part.is_empty() {
69 continue;
70 }
71 let mut found = false;
72 for (op_str, op) in [
73 ("==", Op::Eq),
74 ("!=", Op::Ne),
75 ("<=", Op::Le),
76 (">=", Op::Ge),
77 ("<", Op::Lt),
78 (">", Op::Gt),
79 ] {
80 if let Some(idx) = part.find(op_str) {
81 let field = part[..idx].trim().to_string();
82 let lit_str = part[idx + op_str.len()..].trim();
83 if field.is_empty() || lit_str.is_empty() {
84 return Err(format!("filter: empty field or value in {part:?}").into());
85 }
86 clauses.push(Clause {
87 field,
88 op,
89 literal: parse_literal(lit_str),
90 });
91 found = true;
92 break;
93 }
94 }
95 if !found {
96 return Err(format!("filter: no operator in {part:?}").into());
97 }
98 }
99 if clauses.is_empty() {
100 return Err("filter: no valid clauses".into());
101 }
102 Ok(Self { clauses })
103 }
104
105 pub fn matches(&self, row: &CanonicalValue) -> bool {
107 let obj = match row {
108 CanonicalValue::Object(o) => o,
109 _ => return false,
110 };
111 self.clauses.iter().all(|c| c.matches(obj))
112 }
113}
114
115fn parse_literal(s: &str) -> Literal {
116 if s.eq_ignore_ascii_case("null") {
117 return Literal::Null;
118 }
119 if s.eq_ignore_ascii_case("true") {
120 return Literal::Bool(true);
121 }
122 if s.eq_ignore_ascii_case("false") {
123 return Literal::Bool(false);
124 }
125 if s.len() >= 2 {
126 let b = s.as_bytes();
127 if (b[0] == b'\'' && b[b.len() - 1] == b'\'') || (b[0] == b'"' && b[b.len() - 1] == b'"') {
128 return Literal::String(s[1..s.len() - 1].to_string());
129 }
130 }
131 if let Ok(n) = s.parse::<f64>() {
132 return Literal::Number(n);
133 }
134 Literal::String(s.to_string())
135}
136
137impl Clause {
138 fn matches(&self, obj: &[(String, CanonicalValue)]) -> bool {
139 let cell = match obj.iter().find(|(k, _)| k == &self.field) {
140 Some((_, v)) => v,
141 None => return false,
142 };
143 match self.op {
144 Op::Eq => eq(cell, &self.literal),
145 Op::Ne => !eq(cell, &self.literal),
146 Op::Lt => ord(cell, &self.literal) == Some(std::cmp::Ordering::Less),
147 Op::Le => matches!(
148 ord(cell, &self.literal),
149 Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal)
150 ),
151 Op::Gt => ord(cell, &self.literal) == Some(std::cmp::Ordering::Greater),
152 Op::Ge => matches!(
153 ord(cell, &self.literal),
154 Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal)
155 ),
156 }
157 }
158}
159
160fn as_f64(v: &CanonicalValue) -> Option<f64> {
161 match v {
162 CanonicalValue::Number(n) => n.as_f64(),
163 CanonicalValue::String(s) => s.parse().ok(),
164 CanonicalValue::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
165 _ => None,
166 }
167}
168
169fn eq(cell: &CanonicalValue, lit: &Literal) -> bool {
170 match (cell, lit) {
171 (CanonicalValue::Null, Literal::Null) => true,
172 (CanonicalValue::Bool(b), Literal::Bool(l)) => *b == *l,
173 (CanonicalValue::String(s), Literal::String(l)) => s == l,
174 _ => match (as_f64(cell), as_f64_lit(lit)) {
175 (Some(a), Some(b)) => (a - b).abs() < 1e-10,
176 _ => false,
177 },
178 }
179}
180
181fn as_f64_lit(l: &Literal) -> Option<f64> {
182 match l {
183 Literal::Number(n) => Some(*n),
184 Literal::String(s) => s.parse().ok(),
185 Literal::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
186 Literal::Null => None,
187 }
188}
189
190fn ord(cell: &CanonicalValue, lit: &Literal) -> Option<std::cmp::Ordering> {
191 if let (CanonicalValue::String(a), Literal::String(b)) = (cell, lit) {
192 return Some(a.cmp(b));
193 }
194 let a = as_f64(cell)?;
195 let b = as_f64_lit(lit)?;
196 a.partial_cmp(&b)
197}