use crate::gql::ast::{BinaryOperator, Expression, UnaryOperator};
use crate::storage::GraphStorage;
use crate::value::Value;
use super::Parameters;
#[derive(Debug, Clone)]
pub(super) struct ComparableValue(pub(super) Value);
impl PartialEq for ComparableValue {
fn eq(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(Value::Null, Value::Null) => true,
(Value::Bool(a), Value::Bool(b)) => a == b,
(Value::Int(a), Value::Int(b)) => a == b,
(Value::Float(a), Value::Float(b)) => {
a.to_bits() == b.to_bits()
}
(Value::String(a), Value::String(b)) => a == b,
(Value::Vertex(a), Value::Vertex(b)) => a == b,
(Value::Edge(a), Value::Edge(b)) => a == b,
(Value::List(a), Value::List(b)) => {
if a.len() != b.len() {
return false;
}
a.iter()
.zip(b.iter())
.all(|(x, y)| ComparableValue(x.clone()) == ComparableValue(y.clone()))
}
(Value::Map(a), Value::Map(b)) => {
if a.len() != b.len() {
return false;
}
a.iter().all(|(k, v)| {
b.get(k)
.map(|bv| ComparableValue(v.clone()) == ComparableValue(bv.clone()))
.unwrap_or(false)
})
}
_ => false,
}
}
}
impl Eq for ComparableValue {}
impl std::hash::Hash for ComparableValue {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
std::mem::discriminant(&self.0).hash(state);
match &self.0 {
Value::Null => {}
Value::Bool(b) => b.hash(state),
Value::Int(n) => n.hash(state),
Value::Float(f) => f.to_bits().hash(state),
Value::String(s) => s.hash(state),
Value::Vertex(id) => id.0.hash(state),
Value::Edge(id) => id.0.hash(state),
Value::Point(p) => {
p.lon.to_bits().hash(state);
p.lat.to_bits().hash(state);
}
Value::Polygon(p) => {
for &(lon, lat) in &p.ring {
lon.to_bits().hash(state);
lat.to_bits().hash(state);
}
}
Value::List(items) => {
items.len().hash(state);
for item in items {
ComparableValue(item.clone()).hash(state);
}
}
Value::Map(map) => {
map.len().hash(state);
let mut entries: Vec<_> = map.iter().collect();
entries.sort_by(|(k1, _), (k2, _)| k1.cmp(k2));
for (k, v) in entries {
k.hash(state);
ComparableValue(v.clone()).hash(state);
}
}
}
}
}
impl From<Value> for ComparableValue {
fn from(v: Value) -> Self {
ComparableValue(v)
}
}
impl From<ComparableValue> for Value {
fn from(cv: ComparableValue) -> Self {
cv.0
}
}
pub(super) fn apply_comparison(op: BinaryOperator, left: &Value, right: &Value) -> bool {
match op {
BinaryOperator::Eq => left == right,
BinaryOperator::Neq => left != right,
BinaryOperator::Lt => compare_values(left, right) == std::cmp::Ordering::Less,
BinaryOperator::Lte => {
matches!(
compare_values(left, right),
std::cmp::Ordering::Less | std::cmp::Ordering::Equal
)
}
BinaryOperator::Gt => compare_values(left, right) == std::cmp::Ordering::Greater,
BinaryOperator::Gte => {
matches!(
compare_values(left, right),
std::cmp::Ordering::Greater | std::cmp::Ordering::Equal
)
}
BinaryOperator::And => value_to_bool(left) && value_to_bool(right),
BinaryOperator::Or => value_to_bool(left) || value_to_bool(right),
BinaryOperator::Contains => match (left, right) {
(Value::String(s), Value::String(sub)) => s.contains(sub.as_str()),
_ => false,
},
BinaryOperator::StartsWith => match (left, right) {
(Value::String(s), Value::String(prefix)) => s.starts_with(prefix.as_str()),
_ => false,
},
BinaryOperator::EndsWith => match (left, right) {
(Value::String(s), Value::String(suffix)) => s.ends_with(suffix.as_str()),
_ => false,
},
BinaryOperator::RegexMatch => match (left, right) {
(Value::String(s), Value::String(pattern)) => {
match regex::RegexBuilder::new(pattern)
.size_limit(1024 * 1024) .dfa_size_limit(1024 * 1024) .build()
{
Ok(re) => re.is_match(s),
Err(_) => false, }
}
(Value::Null, _) | (_, Value::Null) => false,
_ => false,
},
_ => false,
}
}
pub(super) fn apply_binary_op(op: BinaryOperator, left: Value, right: Value) -> Value {
match op {
BinaryOperator::Add => match (left, right) {
(Value::Int(a), Value::Int(b)) => Value::Int(a + b),
(Value::Float(a), Value::Float(b)) => Value::Float(a + b),
(Value::Int(a), Value::Float(b)) => Value::Float(a as f64 + b),
(Value::Float(a), Value::Int(b)) => Value::Float(a + b as f64),
(Value::String(a), Value::String(b)) => Value::String(a + b.as_str()),
_ => Value::Null,
},
BinaryOperator::Sub => match (left, right) {
(Value::Int(a), Value::Int(b)) => Value::Int(a - b),
(Value::Float(a), Value::Float(b)) => Value::Float(a - b),
(Value::Int(a), Value::Float(b)) => Value::Float(a as f64 - b),
(Value::Float(a), Value::Int(b)) => Value::Float(a - b as f64),
_ => Value::Null,
},
BinaryOperator::Mul => match (left, right) {
(Value::Int(a), Value::Int(b)) => Value::Int(a * b),
(Value::Float(a), Value::Float(b)) => Value::Float(a * b),
(Value::Int(a), Value::Float(b)) => Value::Float(a as f64 * b),
(Value::Float(a), Value::Int(b)) => Value::Float(a * b as f64),
_ => Value::Null,
},
BinaryOperator::Div => match (left, right) {
(Value::Int(a), Value::Int(b)) if b != 0 => Value::Int(a / b),
(Value::Float(a), Value::Float(b)) if b != 0.0 => Value::Float(a / b),
(Value::Int(a), Value::Float(b)) if b != 0.0 => Value::Float(a as f64 / b),
(Value::Float(a), Value::Int(b)) if b != 0 => Value::Float(a / b as f64),
_ => Value::Null,
},
BinaryOperator::Mod => match (left, right) {
(Value::Int(a), Value::Int(b)) if b != 0 => Value::Int(a % b),
_ => Value::Null,
},
BinaryOperator::Pow => match (left, right) {
(Value::Int(a), Value::Int(b)) if b >= 0 => {
if b > u32::MAX as i64 {
Value::Float((a as f64).powf(b as f64))
} else {
match a.checked_pow(b as u32) {
Some(result) => Value::Int(result),
None => Value::Float((a as f64).powf(b as f64)), }
}
}
(Value::Int(a), Value::Int(b)) => {
let exp = b.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
Value::Float((a as f64).powi(exp))
}
(Value::Float(a), Value::Int(b)) => {
let exp = b.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
Value::Float(a.powi(exp))
}
(Value::Float(a), Value::Float(b)) => Value::Float(a.powf(b)),
(Value::Int(a), Value::Float(b)) => Value::Float((a as f64).powf(b)),
_ => Value::Null,
},
BinaryOperator::Concat => match (&left, &right) {
(Value::Null, _) | (_, Value::Null) => Value::Null,
_ => {
let left_str = value_to_string(&left);
let right_str = value_to_string(&right);
Value::String(format!("{}{}", left_str, right_str))
}
},
op => Value::Bool(apply_comparison(op, &left, &right)),
}
}
pub(super) fn compare_values(left: &Value, right: &Value) -> std::cmp::Ordering {
match (left, right) {
(Value::Int(a), Value::Int(b)) => a.cmp(b),
(Value::Float(a), Value::Float(b)) => a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal),
(Value::Int(a), Value::Float(b)) => (*a as f64)
.partial_cmp(b)
.unwrap_or(std::cmp::Ordering::Equal),
(Value::Float(a), Value::Int(b)) => a
.partial_cmp(&(*b as f64))
.unwrap_or(std::cmp::Ordering::Equal),
(Value::String(a), Value::String(b)) => a.cmp(b),
(Value::Bool(a), Value::Bool(b)) => a.cmp(b),
(Value::Null, Value::Null) => std::cmp::Ordering::Equal,
(Value::Null, _) => std::cmp::Ordering::Less,
(_, Value::Null) => std::cmp::Ordering::Greater,
_ => std::cmp::Ordering::Equal,
}
}
pub(super) fn value_to_bool(val: &Value) -> bool {
match val {
Value::Bool(b) => *b,
Value::Null => false,
Value::Int(n) => *n != 0,
Value::Float(f) => *f != 0.0,
Value::String(s) => !s.is_empty(),
_ => true,
}
}
pub(super) fn value_to_string(val: &Value) -> String {
match val {
Value::String(s) => s.clone(),
Value::Int(n) => n.to_string(),
Value::Float(f) => f.to_string(),
Value::Bool(b) => b.to_string(),
Value::Null => "null".to_string(),
Value::List(items) => {
let inner: Vec<String> = items.iter().map(value_to_string).collect();
format!("[{}]", inner.join(", "))
}
Value::Map(map) => {
let inner: Vec<String> = map
.iter()
.map(|(k, v)| format!("{}: {}", k, value_to_string(v)))
.collect();
format!("{{{}}}", inner.join(", "))
}
Value::Vertex(vid) => format!("Vertex({})", vid.0),
Value::Edge(eid) => format!("Edge({})", eid.0),
Value::Point(p) => p.to_string(),
Value::Polygon(p) => p.to_string(),
}
}
pub(super) fn extract_property_from_storage(
storage: &dyn GraphStorage,
element: &Value,
property: &str,
) -> Option<Value> {
match element {
Value::Vertex(id) => {
let vertex = storage.get_vertex(*id)?;
vertex.properties.get(property).cloned()
}
Value::Edge(id) => {
let edge = storage.get_edge(*id)?;
edge.properties.get(property).cloned()
}
Value::Null => Some(Value::Null),
_ => None,
}
}
pub(super) fn eval_inline_value(
storage: &dyn GraphStorage,
expr: &Expression,
element: &Value,
params: &Parameters,
) -> Value {
match expr {
Expression::Literal(lit) => lit.clone().into(),
Expression::Variable(_) => {
element.clone()
}
Expression::Parameter(name) => {
params.get(name).cloned().unwrap_or(Value::Null)
}
Expression::Property { property, .. } => {
extract_property_from_storage(storage, element, property).unwrap_or(Value::Null)
}
Expression::BinaryOp { left, op, right } => {
let left_val = eval_inline_value(storage, left, element, params);
let right_val = eval_inline_value(storage, right, element, params);
apply_binary_op(*op, left_val, right_val)
}
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => {
let val = eval_inline_value(storage, expr, element, params);
match val {
Value::Bool(b) => Value::Bool(!b),
_ => Value::Null,
}
}
UnaryOperator::Neg => {
let val = eval_inline_value(storage, expr, element, params);
match val {
Value::Int(n) => Value::Int(-n),
Value::Float(f) => Value::Float(-f),
_ => Value::Null,
}
}
},
Expression::IsNull { expr, negated } => {
let val = eval_inline_value(storage, expr, element, params);
let is_null = matches!(val, Value::Null);
Value::Bool(if *negated { !is_null } else { is_null })
}
Expression::InList {
expr,
list,
negated,
} => {
let val = eval_inline_value(storage, expr, element, params);
let in_list = list.iter().any(|item| {
let item_val = eval_inline_value(storage, item, element, params);
val == item_val
});
Value::Bool(if *negated { !in_list } else { in_list })
}
Expression::List(items) => {
let values: Vec<Value> = items
.iter()
.map(|item| eval_inline_value(storage, item, element, params))
.collect();
Value::List(values)
}
Expression::Map(entries) => {
let map: crate::value::ValueMap = entries
.iter()
.map(|(key, value_expr)| {
let value = eval_inline_value(storage, value_expr, element, params);
(key.clone(), value)
})
.collect();
Value::Map(map)
}
_ => Value::Null,
}
}
pub(super) fn eval_inline_predicate(
storage: &dyn GraphStorage,
expr: &Expression,
element: &Value,
params: &Parameters,
) -> bool {
match expr {
Expression::BinaryOp { left, op, right } => {
match op {
BinaryOperator::And => {
eval_inline_predicate(storage, left, element, params)
&& eval_inline_predicate(storage, right, element, params)
}
BinaryOperator::Or => {
eval_inline_predicate(storage, left, element, params)
|| eval_inline_predicate(storage, right, element, params)
}
_ => {
let left_val = eval_inline_value(storage, left, element, params);
let right_val = eval_inline_value(storage, right, element, params);
apply_comparison(*op, &left_val, &right_val)
}
}
}
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => !eval_inline_predicate(storage, expr, element, params),
UnaryOperator::Neg => {
match eval_inline_value(storage, expr, element, params) {
Value::Int(n) => n == 0,
Value::Float(f) => f == 0.0,
Value::Bool(b) => !b,
Value::Null => true,
_ => false,
}
}
},
Expression::IsNull { expr, negated } => {
let val = eval_inline_value(storage, expr, element, params);
let is_null = matches!(val, Value::Null);
if *negated {
!is_null
} else {
is_null
}
}
Expression::InList {
expr,
list,
negated,
} => {
let val = eval_inline_value(storage, expr, element, params);
let in_list = list.iter().any(|item| {
let item_val = eval_inline_value(storage, item, element, params);
val == item_val
});
if *negated {
!in_list
} else {
in_list
}
}
_ => {
let val = eval_inline_value(storage, expr, element, params);
value_to_bool(&val)
}
}
}