use crate::operator::Op;
use crate::value::Value;
use std::collections::{BTreeMap, HashMap, HashSet};
pub trait DnfField {
fn evaluate(&self, op: &Op, value: &Value) -> bool;
}
macro_rules! impl_dnf_field_str {
($($ty:ty),*) => {
$(
impl DnfField for $ty {
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
op.scalar_str(self, value)
}
}
)*
};
}
impl_dnf_field_str!(String, str, Box<str>);
impl DnfField for std::borrow::Cow<'_, str> {
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
op.scalar_str(self.as_ref(), value)
}
}
macro_rules! impl_dnf_field_numeric {
($method:ident as $cast:ty => $($ty:ty),*) => {
$(
impl DnfField for $ty {
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
op.$method(*self as $cast, value)
}
}
)*
};
}
impl_dnf_field_numeric!(scalar_int as i64 => i8, i16, i32, i64, isize);
impl_dnf_field_numeric!(scalar_uint as u64 => u8, u16, u32, u64, usize);
impl_dnf_field_numeric!(scalar_float as f64 => f32, f64);
impl DnfField for bool {
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
op.scalar_bool(*self, value)
}
}
impl DnfField for Value {
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
match self {
Value::String(s) => op.scalar_str(s, value),
Value::Int(n) => op.scalar_int(*n, value),
Value::Uint(n) => op.scalar_uint(*n, value),
Value::Float(f) => op.scalar_float(*f, value),
Value::Bool(b) => op.scalar_bool(*b, value),
Value::None => {
match &op.base {
crate::operator::BaseOperator::Eq => {
let result = matches!(value, Value::None);
if op.inverse {
!result
} else {
result
}
}
_ => false,
}
}
_ => false,
}
}
}
impl<T> DnfField for Vec<T>
where
T: PartialEq<Value> + PartialOrd<Value>,
{
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
op.any(self.iter(), value)
}
}
impl<T> DnfField for HashSet<T>
where
T: PartialEq<Value> + PartialOrd<Value> + Eq + std::hash::Hash,
{
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
op.any(self.iter(), value)
}
}
impl<T> DnfField for Option<T>
where
T: DnfField,
{
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
match self {
Some(v) => v.evaluate(op, value),
None => Value::None.evaluate(op, value),
}
}
}
macro_rules! impl_dnf_field_map {
($($map_type:ident),*) => {
$(
impl<V> DnfField for $map_type<String, V>
where
V: DnfField + PartialEq<Value> + PartialOrd<Value>,
{
#[inline]
fn evaluate(&self, op: &Op, value: &Value) -> bool {
match value {
Value::AtKey(key, inner) => match self.get(key.as_ref()) {
Some(v) => v.evaluate(op, inner),
None => Value::None.evaluate(op, inner),
},
Value::Keys(inner) => op.any(self.keys(), inner),
Value::Values(inner) => op.any(self.values(), inner),
_ => false,
}
}
}
)*
};
}
impl_dnf_field_map!(HashMap, BTreeMap);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_option_evaluation_semantics() {
struct TestCase {
name: &'static str,
field: Option<u32>,
op: Op,
value: Value,
expected: bool,
}
let test_cases = vec![
TestCase {
name: "None == null",
field: None,
op: Op::EQ,
value: Value::None,
expected: true,
},
TestCase {
name: "None != null",
field: None,
op: Op::NE,
value: Value::None,
expected: false,
},
TestCase {
name: "None > 0",
field: None,
op: Op::GT,
value: Value::Uint(0),
expected: false,
},
TestCase {
name: "None < 100",
field: None,
op: Op::LT,
value: Value::Uint(100),
expected: false,
},
TestCase {
name: "None >= 0",
field: None,
op: Op::GTE,
value: Value::Uint(0),
expected: false,
},
TestCase {
name: "None <= 100",
field: None,
op: Op::LTE,
value: Value::Uint(100),
expected: false,
},
TestCase {
name: "None BETWEEN [0, 100]",
field: None,
op: Op::BETWEEN,
value: Value::UintArray(vec![0, 100].into()),
expected: false,
},
TestCase {
name: "Some(5) > 0",
field: Some(5),
op: Op::GT,
value: Value::Uint(0),
expected: true,
},
TestCase {
name: "Some(5) < 100",
field: Some(5),
op: Op::LT,
value: Value::Uint(100),
expected: true,
},
TestCase {
name: "Some(5) == null",
field: Some(5),
op: Op::EQ,
value: Value::None,
expected: false,
},
TestCase {
name: "Some(5) != null",
field: Some(5),
op: Op::NE,
value: Value::None,
expected: true,
},
TestCase {
name: "Some(5) == 5",
field: Some(5),
op: Op::EQ,
value: Value::Uint(5),
expected: true,
},
TestCase {
name: "Some(5) != 10",
field: Some(5),
op: Op::NE,
value: Value::Uint(10),
expected: true,
},
TestCase {
name: "Some(5) BETWEEN [0, 100]",
field: Some(5),
op: Op::BETWEEN,
value: Value::UintArray(vec![0, 100].into()),
expected: true,
},
];
for case in test_cases {
let result = case.field.evaluate(&case.op, &case.value);
assert_eq!(
result, case.expected,
"Test '{}' failed: expected {}, got {}",
case.name, case.expected, result
);
}
}
}