nodedb_query/expr/
binary.rs1use rust_decimal::Decimal;
6
7use nodedb_types::Value;
8
9use crate::value_ops::{
10 coerced_eq, is_truthy, partial_compare_values, to_value_number, value_to_display_string,
11 value_to_f64,
12};
13
14use super::eval::EvalError;
15use super::types::BinaryOp;
16
17fn value_to_decimal(v: &Value) -> Option<Decimal> {
25 match v {
26 Value::Decimal(d) => Some(*d),
27 Value::Integer(i) => Some(Decimal::from(*i)),
28 Value::Float(f) => Decimal::try_from(*f).ok(),
29 _ => None,
30 }
31}
32
33fn decimal_arith(a: Decimal, op: BinaryOp, b: Decimal) -> Result<Value, EvalError> {
40 let result = match op {
41 BinaryOp::Add => a.checked_add(b),
42 BinaryOp::Sub => a.checked_sub(b),
43 BinaryOp::Mul => a.checked_mul(b),
44 BinaryOp::Div => {
45 if b.is_zero() {
46 return Err(EvalError::DivisionByZero);
47 }
48 a.checked_div(b)
49 }
50 BinaryOp::Mod => {
51 if b.is_zero() {
52 return Err(EvalError::DivisionByZero);
53 }
54 a.checked_rem(b)
55 }
56 _ => return Ok(Value::Null),
57 };
58 Ok(result.map(Value::Decimal).unwrap_or(Value::Null))
59}
60
61pub(super) fn eval_binary_op(
62 left: &Value,
63 op: BinaryOp,
64 right: &Value,
65) -> Result<Value, EvalError> {
66 match op {
67 BinaryOp::Add | BinaryOp::Sub | BinaryOp::Mul | BinaryOp::Div | BinaryOp::Mod => {
69 let left_is_decimal = matches!(left, Value::Decimal(_));
70 let right_is_decimal = matches!(right, Value::Decimal(_));
71 if left_is_decimal || right_is_decimal {
72 return match (value_to_decimal(left), value_to_decimal(right)) {
73 (Some(a), Some(b)) => decimal_arith(a, op, b),
74 _ => Ok(Value::Null),
75 };
76 }
77 match op {
79 BinaryOp::Add => Ok(
80 match (value_to_f64(left, true), value_to_f64(right, true)) {
81 (Some(a), Some(b)) => to_value_number(a + b),
82 _ => Value::Null,
83 },
84 ),
85 BinaryOp::Sub => Ok(
86 match (value_to_f64(left, true), value_to_f64(right, true)) {
87 (Some(a), Some(b)) => to_value_number(a - b),
88 _ => Value::Null,
89 },
90 ),
91 BinaryOp::Mul => Ok(
92 match (value_to_f64(left, true), value_to_f64(right, true)) {
93 (Some(a), Some(b)) => to_value_number(a * b),
94 _ => Value::Null,
95 },
96 ),
97 BinaryOp::Div => match (value_to_f64(left, true), value_to_f64(right, true)) {
98 (Some(a), Some(b)) => {
99 if b == 0.0 {
100 Err(EvalError::DivisionByZero)
101 } else {
102 Ok(to_value_number(a / b))
103 }
104 }
105 _ => Ok(Value::Null),
106 },
107 BinaryOp::Mod => match (value_to_f64(left, true), value_to_f64(right, true)) {
108 (Some(a), Some(b)) => {
109 if b == 0.0 {
110 Err(EvalError::DivisionByZero)
111 } else {
112 Ok(to_value_number(a % b))
113 }
114 }
115 _ => Ok(Value::Null),
116 },
117 _ => Ok(Value::Null),
118 }
119 }
120 BinaryOp::Concat => {
121 let ls = value_to_display_string(left);
122 let rs = value_to_display_string(right);
123 Ok(Value::String(format!("{ls}{rs}")))
124 }
125 BinaryOp::Eq => Ok(Value::Bool(coerced_eq(left, right))),
126 BinaryOp::NotEq => Ok(Value::Bool(!coerced_eq(left, right))),
127 BinaryOp::Gt => Ok(Value::Bool(matches!(
128 partial_compare_values(left, right),
129 Some(std::cmp::Ordering::Greater)
130 ))),
131 BinaryOp::GtEq => Ok(Value::Bool(matches!(
132 partial_compare_values(left, right),
133 Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
134 ))),
135 BinaryOp::Lt => Ok(Value::Bool(matches!(
136 partial_compare_values(left, right),
137 Some(std::cmp::Ordering::Less)
138 ))),
139 BinaryOp::LtEq => Ok(Value::Bool(matches!(
140 partial_compare_values(left, right),
141 Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
142 ))),
143 BinaryOp::And => Ok(Value::Bool(is_truthy(left) && is_truthy(right))),
144 BinaryOp::Or => Ok(Value::Bool(is_truthy(left) || is_truthy(right))),
145 }
146}