tudor-sql 0.2.0

Does sql stuff to todo.txt files
Documentation
// TODO rm me
#![allow(warnings)]

use std::collections::BTreeSet;

use pest_consume::Parser;

use crate::sql::expression::function::Function;
use crate::sql::expression::operator::comparison::ComparisonOperator;
use crate::sql::expression::parser::{ExpressionParser, Result, Rule};
use crate::sql::expression::todo_field::TodoField;
use crate::sql::expression::value::Value;
use crate::todo::Todo;

mod function;
mod operator;
mod parser;
pub mod todo_field;
mod value;

/// adapted from https://github.com/ballista-compute/sqlparser-rs/blob/main/src/ast/mod.rs
#[derive(Eq, PartialEq, Debug)]
pub enum Expression {
    /// Identifier e.g. table name or column name
    Identifier(TodoField),
    /// A literal value, such as string, number, date or NULL
    Value(Value),
    /// Unary operation e.g. `NOT foo`
    Negated(Box<Expression>),
    /// Scalar function call e.g. `LEFT(foo, 5)`
    Function(Function),
    /// `<expr> [ NOT ] BETWEEN <low> AND <high>`
    Between {
        expr: Box<Expression>,
        negated: bool,
        low: Box<Expression>,
        high: Box<Expression>,
    },
    /// `[ NOT ] IN (val1, val2, ...)`
    InSet {
        expr: Box<Expression>,
        negated: bool,
        set: Value,
    },
    /// `IS [NOT] [NULL | TRUE | FALSE]` expression
    Is {
        negated: bool,
        left: Box<Expression>,
        right: Value,
    },
    /// Comparison operation e.g. `1 = 1` or `foo > bar`
    ComparisonOp {
        left: Box<Expression>,
        op: ComparisonOperator,
        right: Box<Expression>,
    },
    /// `<expr> AND <expr>`
    And {
        left: Box<Expression>,
        right: Box<Expression>,
    },
    /// `<expr> OR <expr>`
    Or {
        left: Box<Expression>,
        right: Box<Expression>,
    },
    /// `(<expr>)`
    Nested(Box<Expression>),
    // TODO: implement Display
    // TODO: case?
}

impl Expression {
    pub fn eval(&self, t: &Todo) -> Value {
        match self {
            Expression::Identifier(todo_field) => todo_field.get_value(t),
            Expression::Nested(expr) => expr.eval(t),
            Expression::Value(v) => v.clone(),
            Expression::ComparisonOp { left, op, right } => op.apply(&left.eval(t), &right.eval(t)),
            Expression::Is {
                left,
                negated,
                right: Value::Null,
            } => {
                let is_null = left.eval(t).is_null();
                let b = if *negated { !is_null } else { is_null };
                Value::Boolean(b)
            }
            Expression::Is {
                left,
                negated,
                right,
            } => {
                let left = left.eval(t);
                let is = match (&left, right) {
                    (Value::Null, Value::Null) => true,
                    _ => left == *right,
                };
                let b = if *negated { !is } else { is };
                Value::Boolean(b)
            }
            Expression::Function(f) => f.apply(),
            Expression::And { left, right } => {
                let left = left.eval(t);
                if left.is_null() {
                    Value::Null
                } else if left.is_true() {
                    right.eval(t)
                } else if left.is_false() {
                    left
                } else {
                    unreachable!("{:#?} {:#?}", left, right)
                }
            }
            Expression::Or { left, right } => {
                let left = left.eval(t);
                if left.is_null() {
                    Value::Null
                } else if left.is_true() {
                    left
                } else if left.is_false() {
                    right.eval(t)
                } else {
                    unreachable!("{:#?} {:#?}", left, right)
                }
            }
            _ => unimplemented!("{:#?}", self),
        }
    }
    pub fn parse(input: &str) -> Result<Expression> {
        let nodes = ExpressionParser::parse(Rule::expression, input)?;
        let node = nodes.single()?;
        let e = ExpressionParser::expression(node)?;
        Ok(e)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn evaling_is_expressions() {
        let t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();

        let e = Expression::parse("null is null").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("true is true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("false is false").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("null is not null").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));

        let e = Expression::parse("null is not false").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("null is not true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
    }

    #[test]
    fn evaling_expressions() {
        let t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();

        let e = Expression::parse("date('2020-12-30')").unwrap();
        assert_eq!(e.eval(&t), Value::Date(time::date!(2020 - 12 - 30)));
    }

    #[test]
    fn evaling_and_expressions() {
        let t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();

        let e = Expression::parse("null and true").unwrap();
        assert_eq!(e.eval(&t), Value::Null);
        let e = Expression::parse("true and null").unwrap();
        assert_eq!(e.eval(&t), Value::Null);
        let e = Expression::parse("false and null").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));
        let e = Expression::parse("true and ('a' == 'a')").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
        let e = Expression::parse("true and null and true").unwrap();
        assert_eq!(e.eval(&t), Value::Null);
        let e = Expression::parse("true and true and true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
        let e = Expression::parse("true and true and false").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));
        let e = Expression::parse("true and true and null").unwrap();
        assert_eq!(e.eval(&t), Value::Null);
    }

    #[test]
    fn evaling_or_expressions() {
        let t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();

        let e = Expression::parse("null or true").unwrap();
        assert_eq!(e.eval(&t), Value::Null);
        let e = Expression::parse("false or null").unwrap();
        assert_eq!(e.eval(&t), Value::Null);
        let e = Expression::parse("false or true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
        let e = Expression::parse("false or false or true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
        let e = Expression::parse("false or false or true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
        let e = Expression::parse("false or false").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));
        let e = Expression::parse("false or ('a' == 'a')").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
    }

    #[test]
    fn matching_todos_with_expressions() {
        let t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();
        let e = Expression::parse("priority = 'A'").unwrap();

        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("priority = 'B'").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));

        let e = Expression::parse("is_completed = true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("completion_date is null").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("creation_date = date('2020-12-25')").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        // TODO: Test badly fmtd dates
        let e = Expression::parse("creation_date = date('2020-12-31')").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));

        let e = Expression::parse("threshold_date = date('2020-12-30')").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("is_hidden = false").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));
    }

    #[test]
    fn matching_todos_with_and_search_clauses() {
        let t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();

        let e = Expression::parse("is_hidden = false and is_completed = true").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("is_hidden = false and is_completed = false").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));
    }

    #[test]
    fn matching_todos_with_or_search_clauses() {
        let t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();

        // 2nd criteria matches
        let e = Expression::parse("is_completed = false or creation_date = date('2020-12-25')")
            .unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(true));

        let e = Expression::parse("is_hidden = true or is_completed = false").unwrap();
        assert_eq!(e.eval(&t), Value::Boolean(false));
    }

    // TODO: views from https://vapor.visint.in/1_rings/tudor/3-todo-sql/
    //
    //
    // #[test]
    // fn matching_todos_with_context_project_in_search_clauses()
    // TODO: views from https://vapor.visint.in/1_rings/tudor/todo-sql-arrays/
}