From 66239c031db043525186db4a94f6d4b6e05d360b Mon Sep 17 00:00:00 2001
From: EmirVildanov <reddog201030@gmail.com>
Date: Mon, 6 Mar 2023 15:16:32 +0300
Subject: [PATCH] feat: add statistics transformation Value methods

---
 sbroad-core/src/ir/value.rs        | 316 ++++++++++++++++++++++++
 sbroad-core/src/ir/value/double.rs |   2 +-
 sbroad-core/src/ir/value/tests.rs  | 376 +++++++++++++++++++++++++++++
 3 files changed, 693 insertions(+), 1 deletion(-)

diff --git a/sbroad-core/src/ir/value.rs b/sbroad-core/src/ir/value.rs
index 9d98fa787d..f5b4fd85c3 100644
--- a/sbroad-core/src/ir/value.rs
+++ b/sbroad-core/src/ir/value.rs
@@ -1,5 +1,6 @@
 //! Value module.
 
+use std::cmp::Ordering;
 use std::fmt::{self, Display};
 use std::hash::Hash;
 use std::num::NonZeroI32;
@@ -129,6 +130,44 @@ pub enum Value {
     Tuple(Tuple),
 }
 
+/// Custom Ordering using Trivalent instead of simple Equal.
+/// We cannot even derive `PartialOrd` for Values because of Doubles.
+#[derive(Debug, Deserialize, PartialEq, Eq, Serialize)]
+pub enum TrivalentOrdering {
+    Less,
+    Equal,
+    Greater,
+    Unknown,
+}
+
+impl From<Ordering> for TrivalentOrdering {
+    fn from(value: Ordering) -> Self {
+        match value {
+            Ordering::Less => TrivalentOrdering::Less,
+            Ordering::Equal => TrivalentOrdering::Equal,
+            Ordering::Greater => TrivalentOrdering::Greater,
+        }
+    }
+}
+
+impl TrivalentOrdering {
+    /// Transforms `TrivalentOrdering` to Ordering.
+    ///
+    /// # Errors
+    /// Unacceptable `TrivalentOrdering` to transform
+    pub fn to_ordering(&self) -> Result<Ordering, SbroadError> {
+        match self {
+            Self::Less => Ok(Ordering::Less),
+            Self::Equal => Ok(Ordering::Equal),
+            Self::Greater => Ok(Ordering::Greater),
+            Self::Unknown => Err(SbroadError::Invalid(
+                Entity::Value,
+                Some("Can not cast Unknown to Ordering".to_string()),
+            )),
+        }
+    }
+}
+
 /// As a side effect, `NaN == NaN` is true.
 /// We should manually care about this case in the code.
 impl Eq for Value {}
@@ -234,7 +273,155 @@ impl From<Trivalent> for Value {
     }
 }
 
+/// Helper function to extract inner numerical value from `value` and cast it to `Decimal`.
+///
+/// # Errors
+/// - Inner `value` field is not numerical.
+#[allow(dead_code)]
+fn value_to_decimal_or_error(value: &Value) -> Result<Decimal, SbroadError> {
+    match value {
+        Value::Integer(s) => Ok(Decimal::from(*s)),
+        Value::Unsigned(s) => Ok(Decimal::from(*s)),
+        Value::Double(s) => {
+            let from_string_cast = Decimal::from_str(&format!("{s}"));
+            if let Ok(d) = from_string_cast {
+                Ok(d)
+            } else {
+                Err(SbroadError::Invalid(
+                    Entity::Value,
+                    Some(format!("Can't cast {value:?} to decimal")),
+                ))
+            }
+        }
+        Value::Decimal(s) => Ok(*s),
+        _ => Err(SbroadError::Invalid(
+            Entity::Value,
+            Some(format!("{value:?} must be numerical")),
+        )),
+    }
+}
+
 impl Value {
+    /// Adding. Applicable only to numerical values.
+    ///
+    /// # Errors
+    /// - Passed values are not numerical.
+    #[allow(dead_code)]
+    fn add(&self, other: &Value) -> Result<Value, SbroadError> {
+        let self_decimal = value_to_decimal_or_error(self)?;
+        let other_decimal = value_to_decimal_or_error(other)?;
+
+        Ok(Value::from(self_decimal + other_decimal))
+    }
+
+    /// Subtraction. Applicable only to numerical values.
+    ///
+    /// # Errors
+    /// - Passed values are not numerical.
+    #[allow(dead_code)]
+    fn sub(&self, other: &Value) -> Result<Value, SbroadError> {
+        let self_decimal = value_to_decimal_or_error(self)?;
+        let other_decimal = value_to_decimal_or_error(other)?;
+
+        Ok(Value::from(self_decimal - other_decimal))
+    }
+
+    /// Multiplication. Applicable only to numerical values.
+    ///
+    /// # Errors
+    /// - Passed values are not numerical.
+    #[allow(dead_code)]
+    fn mult(&self, other: &Value) -> Result<Value, SbroadError> {
+        let self_decimal = value_to_decimal_or_error(self)?;
+        let other_decimal = value_to_decimal_or_error(other)?;
+
+        Ok(Value::from(self_decimal * other_decimal))
+    }
+
+    /// Division. Applicable only to numerical values.
+    ///
+    /// # Errors
+    /// - Passed values are not numerical.
+    #[allow(dead_code)]
+    fn div(&self, other: &Value) -> Result<Value, SbroadError> {
+        let self_decimal = value_to_decimal_or_error(self)?;
+        let other_decimal = value_to_decimal_or_error(other)?;
+
+        if other_decimal == 0 {
+            Err(SbroadError::Invalid(
+                Entity::Value,
+                Some(format!("Can not divide {self:?} by zero {other:?}")),
+            ))
+        } else {
+            Ok(Value::from(self_decimal / other_decimal))
+        }
+    }
+
+    /// Negation. Applicable only to numerical values.
+    ///
+    /// # Errors
+    /// - Passed value is not numerical.
+    #[allow(dead_code)]
+    fn negate(&self) -> Result<Value, SbroadError> {
+        let self_decimal = value_to_decimal_or_error(self)?;
+
+        Ok(Value::from(-self_decimal))
+    }
+
+    /// Concatenation. Applicable only to `Value::String`.
+    ///
+    /// # Errors
+    /// - Passed values are not `Value::String`.
+    #[allow(dead_code)]
+    fn concat(&self, other: &Value) -> Result<Value, SbroadError> {
+        let (Value::String(s), Value::String(o)) = (self, other) else {
+            return Err(
+                SbroadError::Invalid(
+                    Entity::Value,
+                    Some(format!("{self:?} and {other:?} must be strings to be concatenated"))
+                )
+            )
+        };
+
+        Ok(Value::from(format!("{s}{o}")))
+    }
+
+    /// Logical AND. Applicable only to `Value::Boolean`.
+    ///
+    /// # Errors
+    /// - Passed values are not `Value::Boolean`.
+    #[allow(dead_code)]
+    fn and(&self, other: &Value) -> Result<Value, SbroadError> {
+        let (Value::Boolean(s), Value::Boolean(o)) = (self, other) else {
+            return Err(
+                SbroadError::Invalid(
+                    Entity::Value,
+                    Some(format!("{self:?} and {other:?} must be booleans to be applied to AND operation"))
+                )
+            )
+        };
+
+        Ok(Value::from(*s && *o))
+    }
+
+    /// Logical OR. Applicable only to `Value::Boolean`.
+    ///
+    /// # Errors
+    /// - Passed values are not `Value::Boolean`.
+    #[allow(dead_code)]
+    fn or(&self, other: &Value) -> Result<Value, SbroadError> {
+        let (Value::Boolean(s), Value::Boolean(o)) = (self, other) else {
+            return Err(
+                SbroadError::Invalid(
+                    Entity::Value,
+                    Some(format!("{self:?} and {other:?} must be booleans to be applied to OR operation"))
+                )
+            )
+        };
+
+        Ok(Value::from(*s || *o))
+    }
+
     /// Checks equality of the two values.
     /// The result uses three-valued logic.
     #[must_use]
@@ -317,6 +504,135 @@ impl Value {
         }
     }
 
+    /// Compares two values.
+    /// The result uses four-valued logic (standard `Ordering` variants and
+    /// `Unknown` in case `Null` was met).
+    ///
+    /// Returns `None` in case of
+    /// * String casting Error or types mismatch.
+    /// * Float `NaN` comparison occured.
+    #[must_use]
+    #[allow(clippy::too_many_lines)]
+    pub fn partial_cmp(&self, other: &Value) -> Option<TrivalentOrdering> {
+        match self {
+            Value::Boolean(s) => match other {
+                Value::Boolean(o) => TrivalentOrdering::from(s.cmp(o)).into(),
+                Value::Null => TrivalentOrdering::Unknown.into(),
+                Value::Unsigned(_)
+                | Value::Integer(_)
+                | Value::Decimal(_)
+                | Value::Double(_)
+                | Value::String(_)
+                | Value::Tuple(_) => None,
+            },
+            Value::Null => TrivalentOrdering::Unknown.into(),
+            Value::Integer(s) => match other {
+                Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => None,
+                Value::Null => TrivalentOrdering::Unknown.into(),
+                Value::Integer(o) => TrivalentOrdering::from(s.cmp(o)).into(),
+                Value::Decimal(o) => TrivalentOrdering::from(Decimal::from(*s).cmp(o)).into(),
+                // If double can't be converted to decimal without error then it is not equal to integer.
+                Value::Double(o) => {
+                    let self_converted = Decimal::from_str(&format!("{s}"));
+                    let other_converted = Decimal::from_str(&format!("{o}"));
+                    match (self_converted, other_converted) {
+                        (Ok(d1), Ok(d2)) => TrivalentOrdering::from(d1.cmp(&d2)).into(),
+                        _ => None,
+                    }
+                }
+                Value::Unsigned(o) => {
+                    TrivalentOrdering::from(Decimal::from(*s).cmp(&Decimal::from(*o))).into()
+                }
+            },
+            Value::Double(s) => match other {
+                Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => None,
+                Value::Null => TrivalentOrdering::Unknown.into(),
+                Value::Integer(o) => {
+                    if let Some(ord) = s.partial_cmp(&Double::from(*o)) {
+                        TrivalentOrdering::from(ord).into()
+                    } else {
+                        None
+                    }
+                }
+                // If double can't be converted to decimal without error then it is not equal to decimal.
+                Value::Decimal(o) => {
+                    if let Ok(d) = Decimal::from_str(&format!("{s}")) {
+                        TrivalentOrdering::from(d.cmp(o)).into()
+                    } else {
+                        None
+                    }
+                }
+                Value::Double(o) => {
+                    if let Some(ord) = s.partial_cmp(o) {
+                        TrivalentOrdering::from(ord).into()
+                    } else {
+                        None
+                    }
+                }
+                // If double can't be converted to decimal without error then it is not equal to unsigned.
+                Value::Unsigned(o) => {
+                    if let Ok(d) = Decimal::from_str(&format!("{s}")) {
+                        TrivalentOrdering::from(d.cmp(&Decimal::from(*o))).into()
+                    } else {
+                        None
+                    }
+                }
+            },
+            Value::Decimal(s) => match other {
+                Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => None,
+                Value::Null => TrivalentOrdering::Unknown.into(),
+                Value::Integer(o) => TrivalentOrdering::from(s.cmp(&Decimal::from(*o))).into(),
+                Value::Decimal(o) => TrivalentOrdering::from(s.cmp(o)).into(),
+                // If double can't be converted to decimal without error then it is not equal to decimal.
+                Value::Double(o) => {
+                    if let Ok(d) = Decimal::from_str(&format!("{o}")) {
+                        TrivalentOrdering::from(s.cmp(&d)).into()
+                    } else {
+                        None
+                    }
+                }
+                Value::Unsigned(o) => TrivalentOrdering::from(s.cmp(&Decimal::from(*o))).into(),
+            },
+            Value::Unsigned(s) => match other {
+                Value::Boolean(_) | Value::String(_) | Value::Tuple(_) => None,
+                Value::Null => TrivalentOrdering::Unknown.into(),
+                Value::Integer(o) => {
+                    TrivalentOrdering::from(Decimal::from(*s).cmp(&Decimal::from(*o))).into()
+                }
+                Value::Decimal(o) => TrivalentOrdering::from(Decimal::from(*s).cmp(o)).into(),
+                // If double can't be converted to decimal without error then it is not equal to unsigned.
+                Value::Double(o) => {
+                    if let Ok(d) = Decimal::from_str(&format!("{o}")) {
+                        TrivalentOrdering::from(Decimal::from(*s).cmp(&d)).into()
+                    } else {
+                        None
+                    }
+                }
+                Value::Unsigned(o) => TrivalentOrdering::from(s.cmp(o)).into(),
+            },
+            Value::String(s) => match other {
+                Value::Boolean(_)
+                | Value::Integer(_)
+                | Value::Decimal(_)
+                | Value::Double(_)
+                | Value::Unsigned(_)
+                | Value::Tuple(_) => None,
+                Value::Null => TrivalentOrdering::Unknown.into(),
+                Value::String(o) => TrivalentOrdering::from(s.cmp(o)).into(),
+            },
+            Value::Tuple(_) => match other {
+                Value::Boolean(_)
+                | Value::Integer(_)
+                | Value::Decimal(_)
+                | Value::Double(_)
+                | Value::Unsigned(_)
+                | Value::String(_)
+                | Value::Tuple(_) => None,
+                Value::Null => TrivalentOrdering::Unknown.into(),
+            },
+        }
+    }
+
     /// Cast a value to a different type.
     ///
     /// # Errors
diff --git a/sbroad-core/src/ir/value/double.rs b/sbroad-core/src/ir/value/double.rs
index e332144c91..19388f7680 100644
--- a/sbroad-core/src/ir/value/double.rs
+++ b/sbroad-core/src/ir/value/double.rs
@@ -9,7 +9,7 @@ use crate::errors::{Entity, SbroadError};
 use serde::{Deserialize, Serialize};
 use tarantool::tlua;
 
-#[derive(Serialize, Deserialize, PartialEq, Debug, Clone)]
+#[derive(Serialize, Deserialize, PartialEq, PartialOrd, Debug, Clone)]
 pub struct Double {
     pub value: f64,
 }
diff --git a/sbroad-core/src/ir/value/tests.rs b/sbroad-core/src/ir/value/tests.rs
index 8a7bf83782..d4153af171 100644
--- a/sbroad-core/src/ir/value/tests.rs
+++ b/sbroad-core/src/ir/value/tests.rs
@@ -221,6 +221,369 @@ fn equivalence() {
     assert_eq!(Trivalent::Unknown, Value::from("hello").eq(&Value::Null));
 }
 
+#[test]
+#[allow(clippy::too_many_lines)]
+fn partial_comparing() {
+    // Boolean
+    assert_eq!(
+        TrivalentOrdering::Greater,
+        Value::Boolean(true)
+            .partial_cmp(&Value::Boolean(false))
+            .unwrap()
+    );
+    assert_eq!(
+        None,
+        Value::Boolean(true).partial_cmp(&Value::from(Double::from(1e0_f64)))
+    );
+    assert_eq!(
+        None,
+        Value::Boolean(true).partial_cmp(&Value::from(decimal!(1e0)))
+    );
+    assert_eq!(None, Value::Boolean(false).partial_cmp(&Value::from(0_u64)));
+    assert_eq!(None, Value::Boolean(true).partial_cmp(&Value::from(1_i64)));
+    assert_eq!(None, Value::Boolean(false).partial_cmp(&Value::from("")));
+    assert_eq!(
+        None,
+        Value::Boolean(true).partial_cmp(&Value::from("hello"))
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Boolean(true)
+            .partial_cmp(&Value::Boolean(true))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Boolean(true).partial_cmp(&Value::Null).unwrap()
+    );
+
+    // Decimal
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Decimal(decimal!(0.000))
+            .partial_cmp(&Value::from(0_u64))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Decimal(decimal!(0.000))
+            .partial_cmp(&Value::from(decimal!(0)))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Decimal(decimal!(0.000))
+            .partial_cmp(&Value::from(0_u64))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Decimal(decimal!(0.000))
+            .partial_cmp(&Value::from(0_i64))
+            .unwrap()
+    );
+    assert_eq!(
+        None,
+        Value::Decimal(decimal!(0.000)).partial_cmp(&Value::from(false))
+    );
+    assert_eq!(
+        None,
+        Value::Decimal(decimal!(0.000)).partial_cmp(&Value::from(""))
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Decimal(decimal!(0.000))
+            .partial_cmp(&Value::Null)
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Less,
+        Value::Decimal(decimal!(0.000))
+            .partial_cmp(&Value::Decimal(decimal!(1.000)))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Less,
+        Value::Decimal(decimal!(0.000))
+            .partial_cmp(&Value::Unsigned(1))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Greater,
+        Value::Decimal(decimal!(1.000))
+            .partial_cmp(&Value::Unsigned(0))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Greater,
+        Value::Decimal(decimal!(1.000))
+            .partial_cmp(&Value::Double(Double { value: 0.0 }))
+            .unwrap()
+    );
+
+    // Double
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Double(Double::from(0.000_f64))
+            .partial_cmp(&Value::from(0_u64))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Double(Double::from(0.000_f64))
+            .partial_cmp(&Value::from(0_i64))
+            .unwrap()
+    );
+    assert_eq!(
+        None,
+        Value::Double(Double::from(0.000_f64)).partial_cmp(&Value::from(false))
+    );
+    assert_eq!(
+        None,
+        Value::Double(Double::from(0.000_f64)).partial_cmp(&Value::from(""))
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Double(Double::from(0.000_f64))
+            .partial_cmp(&Value::Null)
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Double(Double::from(f64::INFINITY))
+            .partial_cmp(&Value::from(f64::INFINITY))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::Double(Double::from(f64::NEG_INFINITY))
+            .partial_cmp(&Value::from(f64::NEG_INFINITY))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Double(Double::from(f64::NAN))
+            .partial_cmp(&Value::from(f64::NAN))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Less,
+        Value::Double(Double::from(0.0))
+            .partial_cmp(&Value::from(1))
+            .unwrap()
+    );
+    assert_eq!(
+        None,
+        Value::Double(Double::from(f64::NAN)).partial_cmp(&Value::Double(Double::from(1.0)))
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Double(Double::from(1.0))
+            .partial_cmp(&Value::from(f64::NAN))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Greater,
+        Value::Double(Double::from(1.0))
+            .partial_cmp(&Value::from(0.5))
+            .unwrap()
+    );
+
+    // Null
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Null.partial_cmp(&Value::Null).unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Null.partial_cmp(&Value::Boolean(false)).unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Null
+            .partial_cmp(&Value::Double(f64::NAN.into()))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::Null.partial_cmp(&Value::from("")).unwrap()
+    );
+
+    // String
+    assert_eq!(
+        TrivalentOrdering::Less,
+        Value::from("hello")
+            .partial_cmp(&Value::from("hello "))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Equal,
+        Value::from("hello")
+            .partial_cmp(&Value::from("hello".to_string()))
+            .unwrap()
+    );
+    assert_eq!(
+        TrivalentOrdering::Unknown,
+        Value::from("hello").partial_cmp(&Value::Null).unwrap()
+    );
+}
+
+#[test]
+#[allow(clippy::too_many_lines)]
+fn arithmetic() {
+    // Add
+    assert_eq!(
+        Value::Decimal(decimal!(1.000)),
+        Value::Decimal(decimal!(0.000))
+            .add(&Value::from(1.000))
+            .unwrap()
+    );
+    assert_eq!(
+        Value::Decimal(decimal!(1.000)),
+        Value::Decimal(decimal!(0.000))
+            .add(&Value::Double(Double { value: 1.0 }))
+            .unwrap()
+    );
+    assert_eq!(
+        Err(SbroadError::Invalid(
+            Entity::Value,
+            Some(format!(
+                "Can't cast Double(Double {{ value: NaN }}) to decimal"
+            )),
+        )),
+        Value::Double(Double::from(f64::NAN)).add(&Value::Integer(1))
+    );
+
+    // Sub
+    assert_eq!(
+        Value::Decimal(decimal!(1.000)),
+        Value::Decimal(decimal!(2.000))
+            .sub(&Value::Double(Double { value: 1.0 }))
+            .unwrap()
+    );
+    assert_eq!(
+        Value::Decimal(decimal!(5.500)),
+        Value::Decimal(decimal!(8.000))
+            .sub(&Value::from(2.500))
+            .unwrap()
+    );
+
+    // Mult
+    assert_eq!(
+        Value::Decimal(decimal!(8.000)),
+        Value::Decimal(decimal!(2.000))
+            .mult(&Value::from(4))
+            .unwrap()
+    );
+    assert_eq!(
+        Value::Decimal(decimal!(3.999)),
+        Value::from(3).mult(&Value::from(1.333)).unwrap()
+    );
+    assert_eq!(
+        Value::Decimal(decimal!(555)),
+        Value::from(5.0).mult(&Value::Unsigned(111)).unwrap()
+    );
+
+    // Div
+    assert_eq!(
+        Value::Decimal(decimal!(3)),
+        Value::from(9.0).div(&Value::Unsigned(3)).unwrap()
+    );
+    assert_eq!(
+        Value::Decimal(decimal!(1)),
+        Value::Integer(2).div(&Value::Unsigned(2)).unwrap()
+    );
+    assert_eq!(
+        Err(SbroadError::Invalid(
+            Entity::Value,
+            Some(format!("String(\"\") must be numerical"))
+        )),
+        Value::from("").div(&Value::Unsigned(2))
+    );
+    assert_eq!(
+        Err(SbroadError::Invalid(
+            Entity::Value,
+            Some(format!("Can not divide Integer(1) by zero Integer(0)"))
+        )),
+        Value::Integer(1).div(&Value::Integer(0))
+    );
+
+    // Negate
+    assert_eq!(
+        Value::Decimal(decimal!(-3)),
+        Value::from(3.0).negate().unwrap()
+    );
+    assert_eq!(
+        Value::Decimal(decimal!(-1)),
+        Value::Integer(1).negate().unwrap()
+    );
+    assert_eq!(
+        Value::Decimal(decimal!(0)),
+        Value::Double(Double::from(0.0)).negate().unwrap()
+    );
+}
+
+#[test]
+fn concatenation() {
+    assert_eq!(
+        Value::from("hello"),
+        Value::from("").concat(&Value::from("hello")).unwrap()
+    );
+    assert_eq!(
+        Value::from("ab"),
+        Value::from("a").concat(&Value::from("b")).unwrap()
+    );
+    assert_eq!(
+        Err(SbroadError::Invalid(
+            Entity::Value,
+            Some(format!(
+                "Integer(1) and String(\"b\") must be strings to be concatenated"
+            ))
+        )),
+        Value::Integer(1).concat(&Value::from("b"))
+    )
+}
+
+#[test]
+fn and_or() {
+    // And
+    assert_eq!(
+        Value::from(true),
+        Value::from(true).and(&Value::from(true)).unwrap()
+    );
+    assert_eq!(
+        Value::from(false),
+        Value::from(false).and(&Value::from(true)).unwrap()
+    );
+    assert_eq!(
+        Value::from(false),
+        Value::from(true).and(&Value::from(false)).unwrap()
+    );
+    assert_eq!(
+        Value::from(false),
+        Value::from(false).and(&Value::from(false)).unwrap()
+    );
+
+    // Or
+    assert_eq!(
+        Value::from(true),
+        Value::from(true).or(&Value::from(false)).unwrap()
+    );
+    assert_eq!(
+        Value::from(false),
+        Value::from(false).or(&Value::from(false)).unwrap()
+    );
+    assert_eq!(
+        Err(SbroadError::Invalid(
+            Entity::Value,
+            Some(format!(
+                "Integer(1) and Boolean(false) must be booleans to be applied to OR operation"
+            ))
+        )),
+        Value::Integer(1).or(&Value::from(false))
+    );
+}
+
 #[test]
 fn trivalent() {
     assert_eq!(
@@ -237,3 +600,16 @@ fn trivalent() {
     );
     assert_eq!(Trivalent::Unknown, Value::Null.eq(&Value::Null));
 }
+
+#[test]
+fn trivalent_ordering() {
+    assert_eq!(
+        TrivalentOrdering::Less,
+        TrivalentOrdering::from(false.cmp(&true))
+    );
+    assert_eq!(TrivalentOrdering::Equal, TrivalentOrdering::from(1.cmp(&1)));
+    assert_eq!(
+        TrivalentOrdering::Greater,
+        TrivalentOrdering::from("b".cmp(""))
+    );
+}
-- 
GitLab