csvsql 0.1.4

SQL like engine that works on CSV file.
Documentation
use std::{
    fmt::Display,
    hash::Hash,
    ops::{Add, Deref, Div, Mul, Rem, Sub},
    str::FromStr,
};

use crate::util::SmartReference;
use bigdecimal::BigDecimal;
use bigdecimal::Zero;
use chrono::{NaiveDate, NaiveDateTime};
use thiserror::Error;

#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord, Clone)]
pub enum Value {
    Empty,
    Bool(bool),
    Number(BigDecimal),
    Date(NaiveDate),
    Timestamp(NaiveDateTime),
    Str(String),
}

impl Display for Value {
    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Value::Number(b) => b.fmt(formatter),
            Value::Date(d) => d.format("%Y-%m-%d").fmt(formatter),
            Value::Timestamp(d) => d.format("%Y-%m-%d %H:%M:%S%.f").fmt(formatter),
            Value::Str(str) => str.fmt(formatter),
            Value::Bool(b) => {
                if *b {
                    write!(formatter, "TRUE")
                } else {
                    write!(formatter, "FALSE")
                }
            }
            Value::Empty => write!(formatter, ""),
        }
    }
}
impl From<BigDecimal> for Value {
    fn from(decimal: BigDecimal) -> Self {
        Value::Number(decimal)
    }
}

impl Value {
    fn as_number(&self) -> Option<SmartReference<'_, BigDecimal>> {
        match self {
            Value::Number(bc) => Some(bc.into()),
            _ => None,
        }
    }
    pub(crate) fn is_empty(&self) -> bool {
        matches!(self, Value::Empty)
    }
}
impl From<&str> for Value {
    fn from(value: &str) -> Self {
        if value.is_empty() {
            return Value::Empty;
        }
        if value == "TRUE" {
            return Value::Bool(true);
        }
        if value == "FALSE" {
            return Value::Bool(false);
        }
        if let Ok(date) = NaiveDateTime::parse_from_str(value, "%Y-%m-%d %H:%M:%S%.f") {
            return Value::Timestamp(date);
        }
        if let Ok(date) = NaiveDate::parse_from_str(value, "%Y-%m-%d") {
            return Value::Date(date);
        }
        if let Ok(decimal) = BigDecimal::from_str(value) {
            return decimal.into();
        }
        Value::Str(value.to_string())
    }
}
impl From<bool> for Value {
    fn from(value: bool) -> Self {
        Value::Bool(value)
    }
}

impl Add for &Value {
    type Output = Value;
    fn add(self, rhs: Self) -> Self::Output {
        let Some(me) = self.as_number() else {
            return Value::Empty;
        };
        let Some(other) = rhs.as_number() else {
            return Value::Empty;
        };
        (me.deref() + other.deref()).into()
    }
}
impl Mul for &Value {
    type Output = Value;
    fn mul(self, rhs: Self) -> Self::Output {
        let Some(me) = self.as_number() else {
            return Value::Empty;
        };
        let Some(other) = rhs.as_number() else {
            return Value::Empty;
        };
        (me.deref() * other.deref()).into()
    }
}

impl Div for &Value {
    type Output = Value;
    fn div(self, rhs: Self) -> Self::Output {
        let Some(me) = self.as_number() else {
            return Value::Empty;
        };
        let Some(other) = rhs.as_number() else {
            return Value::Empty;
        };
        if other.is_zero() {
            return Value::Empty;
        }
        (me.deref() / other.deref()).into()
    }
}

impl Sub for &Value {
    type Output = Value;
    fn sub(self, rhs: Self) -> Self::Output {
        let Some(me) = self.as_number() else {
            return Value::Empty;
        };
        let Some(other) = rhs.as_number() else {
            return Value::Empty;
        };
        (me.deref() - other.deref()).into()
    }
}

impl Rem for &Value {
    type Output = Value;
    fn rem(self, rhs: Self) -> Self::Output {
        let Some(me) = self.as_number() else {
            return Value::Empty;
        };
        let Some(other) = rhs.as_number() else {
            return Value::Empty;
        };
        if other.is_zero() {
            return Value::Empty;
        }
        (me.deref() % other.deref()).into()
    }
}

#[derive(Error, Debug)]
pub enum ValueError {
    #[error("Not a number")]
    NotANumber,
}

#[cfg(test)]
mod tests {

    use super::*;
    use bigdecimal::FromPrimitive;

    #[test]
    fn display_int_value() {
        let value = Value::Number(BigDecimal::from_i16(101).unwrap());

        let str = format!("{value}");

        assert_eq!(str, "101");
    }

    #[test]
    fn display_float_value() {
        let value = Value::Number(BigDecimal::from_f32(10.25).unwrap());

        let str = format!("{value}");

        assert_eq!(str, "10.25");
    }

    #[test]
    fn display_true_value() {
        let value = Value::Bool(true);

        let str = format!("{value}");

        assert_eq!(str, "TRUE");
    }

    #[test]
    fn display_false_value() {
        let value = Value::Bool(false);

        let str = format!("{value}");

        assert_eq!(str, "FALSE");
    }

    #[test]
    fn display_big_decimal_value() {
        let value =
            Value::Number(BigDecimal::from_str("12312312312312312312312312313123").unwrap());

        let str = format!("{value}");

        assert_eq!(str, "12312312312312312312312312313123");
    }

    #[test]
    fn display_date_value() {
        let value = Value::Date(NaiveDate::parse_from_str("2018-04-21", "%Y-%m-%d").unwrap());

        let str = format!("{value}");

        assert_eq!(str, "2018-04-21");
    }

    #[test]
    fn display_timestamp_value() {
        let value = Value::Timestamp(
            NaiveDateTime::parse_from_str("2018-04-21 10:12:40.011", "%Y-%m-%d %H:%M:%S%.f")
                .unwrap(),
        );

        let str = format!("{value}");

        assert_eq!(str, "2018-04-21 10:12:40.011");
    }

    #[test]
    fn display_string_value() {
        let value = Value::Str("test".into());

        let str = format!("{value}");

        assert_eq!(str, "test");
    }

    #[test]
    fn display_empty_value() {
        let value = Value::Empty;

        let str = format!("{value}");

        assert_eq!(str, "");
    }

    #[test]
    fn from_str_empty() {
        let str = "";
        let value: Value = str.into();

        let is_empty = matches!(value, Value::Empty);
        assert!(is_empty);
    }

    #[test]
    fn from_str_string() {
        let str = "test";
        let value: Value = str.into();

        let str = match value {
            Value::Str(str) => Some(str),
            _ => None,
        };
        assert_eq!(str, Some("test".into()));
    }

    #[test]
    fn from_timestamp() {
        let str = "2018-04-21 10:12:40";
        let value: Value = str.into();

        let str = match value {
            Value::Timestamp(str) => Some(str),
            _ => None,
        };
        assert_eq!(
            str,
            Some(
                NaiveDateTime::parse_from_str("2018-04-21 10:12:40", "%Y-%m-%d %H:%M:%S%.f")
                    .unwrap()
            )
        );
    }

    #[test]
    fn from_date() {
        let str = "2018-04-21";
        let value: Value = str.into();

        let str = match value {
            Value::Date(str) => Some(str),
            _ => None,
        };
        assert_eq!(
            str,
            Some(NaiveDate::parse_from_str("2018-04-21", "%Y-%m-%d").unwrap())
        );
    }

    #[test]
    fn from_true() {
        let str = "TRUE";
        let value: Value = str.into();

        let str = match value {
            Value::Bool(str) => Some(str),
            _ => None,
        };
        assert_eq!(str, Some(true));
    }

    #[test]
    fn from_false() {
        let str = "FALSE";
        let value: Value = str.into();

        let str = match value {
            Value::Bool(str) => Some(str),
            _ => None,
        };
        assert_eq!(str, Some(false));
    }

    #[test]
    fn from_number() {
        let str = "325123142355765678123412453653.123412453456256456";
        let value: Value = str.into();

        let str = match value {
            Value::Number(str) => Some(str),
            _ => None,
        };
        assert_eq!(
            str,
            Some(
                BigDecimal::from_str("325123142355765678123412453653.123412453456256456").unwrap()
            )
        );
    }
}