use crate::CanonicalValue;
use std::error::Error;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Op {
Eq,
Ne,
Lt,
Le,
Gt,
Ge,
}
#[derive(Debug, Clone)]
pub struct Filter {
clauses: Vec<Clause>,
}
#[derive(Debug, Clone)]
struct Clause {
field: String,
op: Op,
literal: Literal,
}
#[derive(Debug, Clone)]
enum Literal {
Null,
Bool(bool),
Number(f64),
String(String),
}
impl Filter {
pub fn parse(input: &str) -> Result<Self, Box<dyn Error>> {
let mut clauses = Vec::new();
for raw in input.split(" and ") {
let part = raw.trim();
if part.is_empty() {
continue;
}
let mut found = false;
for (op_str, op) in [
("==", Op::Eq),
("!=", Op::Ne),
("<=", Op::Le),
(">=", Op::Ge),
("<", Op::Lt),
(">", Op::Gt),
] {
if let Some(idx) = part.find(op_str) {
let field = part[..idx].trim().to_string();
let lit_str = part[idx + op_str.len()..].trim();
if field.is_empty() || lit_str.is_empty() {
return Err(format!("filter: empty field or value in {part:?}").into());
}
clauses.push(Clause {
field,
op,
literal: parse_literal(lit_str),
});
found = true;
break;
}
}
if !found {
return Err(format!("filter: no operator in {part:?}").into());
}
}
if clauses.is_empty() {
return Err("filter: no valid clauses".into());
}
Ok(Self { clauses })
}
pub fn matches(&self, row: &CanonicalValue) -> bool {
let obj = match row {
CanonicalValue::Object(o) => o,
_ => return false,
};
self.clauses.iter().all(|c| c.matches(obj))
}
}
fn parse_literal(s: &str) -> Literal {
if s.eq_ignore_ascii_case("null") {
return Literal::Null;
}
if s.eq_ignore_ascii_case("true") {
return Literal::Bool(true);
}
if s.eq_ignore_ascii_case("false") {
return Literal::Bool(false);
}
if s.len() >= 2 {
let b = s.as_bytes();
if (b[0] == b'\'' && b[b.len() - 1] == b'\'') || (b[0] == b'"' && b[b.len() - 1] == b'"') {
return Literal::String(s[1..s.len() - 1].to_string());
}
}
if let Ok(n) = s.parse::<f64>() {
return Literal::Number(n);
}
Literal::String(s.to_string())
}
impl Clause {
fn matches(&self, obj: &[(String, CanonicalValue)]) -> bool {
let cell = match obj.iter().find(|(k, _)| k == &self.field) {
Some((_, v)) => v,
None => return false,
};
match self.op {
Op::Eq => eq(cell, &self.literal),
Op::Ne => !eq(cell, &self.literal),
Op::Lt => ord(cell, &self.literal) == Some(std::cmp::Ordering::Less),
Op::Le => matches!(
ord(cell, &self.literal),
Some(std::cmp::Ordering::Less) | Some(std::cmp::Ordering::Equal)
),
Op::Gt => ord(cell, &self.literal) == Some(std::cmp::Ordering::Greater),
Op::Ge => matches!(
ord(cell, &self.literal),
Some(std::cmp::Ordering::Greater) | Some(std::cmp::Ordering::Equal)
),
}
}
}
fn as_f64(v: &CanonicalValue) -> Option<f64> {
match v {
CanonicalValue::Number(n) => n.as_f64(),
CanonicalValue::String(s) => s.parse().ok(),
CanonicalValue::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
_ => None,
}
}
fn eq(cell: &CanonicalValue, lit: &Literal) -> bool {
match (cell, lit) {
(CanonicalValue::Null, Literal::Null) => true,
(CanonicalValue::Bool(b), Literal::Bool(l)) => *b == *l,
(CanonicalValue::String(s), Literal::String(l)) => s == l,
_ => match (as_f64(cell), as_f64_lit(lit)) {
(Some(a), Some(b)) => (a - b).abs() < 1e-10,
_ => false,
},
}
}
fn as_f64_lit(l: &Literal) -> Option<f64> {
match l {
Literal::Number(n) => Some(*n),
Literal::String(s) => s.parse().ok(),
Literal::Bool(b) => Some(if *b { 1.0 } else { 0.0 }),
Literal::Null => None,
}
}
fn ord(cell: &CanonicalValue, lit: &Literal) -> Option<std::cmp::Ordering> {
if let (CanonicalValue::String(a), Literal::String(b)) = (cell, lit) {
return Some(a.cmp(b));
}
let a = as_f64(cell)?;
let b = as_f64_lit(lit)?;
a.partial_cmp(&b)
}