#![allow(warnings)]
use std::collections::BTreeSet;
use log::*;
use typed_builder::TypedBuilder;
use crate::sql::SelectFields::Star;
use crate::sql::SelectFrom::Stdin;
use crate::todo::Todo;
#[derive(Eq, Ord, PartialOrd, PartialEq, Debug)]
enum Value {
Null,
True,
False,
Date(time::Date),
String_(String),
Set(BTreeSet<Value>),
}
impl Value {
pub fn is_null(&self) -> bool {
match self {
Value::Null => true,
_ => false,
}
}
}
#[derive(Eq, PartialEq, Hash, Debug)]
enum Field {
FullText,
IsCompleted,
CreationDate,
CompletionDate,
Priority,
CanonicalContext,
Contexts,
CanonicalProject,
Projects,
DueDate,
ThresholdDate,
IsHidden,
}
impl Field {
fn map_or_null<T, F: FnOnce(T) -> Value>(o: Option<T>, f: F) -> Value {
match o {
Some(t) => f(t),
None => Value::Null,
}
}
fn bool_to_value(b: bool) -> Value {
if b {
Value::True
} else {
Value::False
}
}
fn get_value(&self, t: &Todo) -> Value {
let val = match self {
Field::FullText => unimplemented!(), Field::IsCompleted => Field::bool_to_value(t.is_completed),
Field::CreationDate => Field::map_or_null(t.creation_date, |d| Value::Date(d)),
Field::CompletionDate => Field::map_or_null(t.completion_date, |d| Value::Date(d)),
Field::Priority => {
Field::map_or_null(t.priority, |pri| Value::String_(pri.to_string()))
}
Field::CanonicalContext => {
Field::map_or_null(t.canonical_context(), |c| Value::String_(c))
}
Field::CanonicalProject => {
Field::map_or_null(t.canonical_project(), |c| Value::String_(c))
}
Field::Contexts => Value::Set(
t.contexts
.iter()
.map(|s| Value::String_(s.to_string()))
.collect(),
),
Field::Projects => Value::Set(
t.contexts
.iter()
.map(|s| Value::String_(s.to_string()))
.collect(),
),
Field::DueDate => Field::map_or_null(t.due_date, |d| Value::Date(d)),
Field::ThresholdDate => Field::map_or_null(t.due_date, |d| Value::Date(d)),
Field::IsHidden => Field::bool_to_value(t.is_hidden),
};
val
}
}
#[derive(Eq, PartialEq, Debug)]
enum SelectFields {
Star,
}
impl Default for SelectFields {
fn default() -> Self {
Star
}
}
#[derive(Eq, PartialEq, Debug)]
enum SelectFrom {
Stdin,
}
impl Default for SelectFrom {
fn default() -> Self {
Stdin
}
}
#[derive(Default, TypedBuilder, Eq, PartialEq, Debug)]
struct Select {
#[builder(default)]
fields: SelectFields,
#[builder(default)]
from: SelectFrom,
}
#[derive(Eq, PartialEq, Debug)]
enum ComparisonOperator {
Equals,
GreaterThan,
LessThan,
Is,
IsNot,
In,
NotIn,
}
impl ComparisonOperator {
fn matches(&self, v1: &Value, v2: &Value) -> bool {
let v1null = v1.is_null();
let v2null = v2.is_null();
match self {
ComparisonOperator::Equals => !v1null && !v2null && v1 == v2,
ComparisonOperator::Is => (v1null && v2null) || v1 == v2,
_ => unimplemented!(),
}
}
}
#[derive(Eq, PartialEq, Debug)]
enum SearchClause {
Field(Field, ComparisonOperator, Value),
And(Vec<SearchClause>),
Or(Vec<SearchClause>),
}
impl SearchClause {
fn matches(&self, t: &Todo) -> bool {
match self {
SearchClause::Field(
Field::CanonicalContext,
ComparisonOperator::In,
Value::Set(haystack),
) => match t.canonical_context() {
None => false,
Some(c) => haystack.contains(&Value::String_(c)),
},
SearchClause::Field(
Field::CanonicalProject,
ComparisonOperator::In,
Value::Set(haystack),
) => match t.canonical_project() {
None => false,
Some(c) => haystack.contains(&Value::String_(c)),
},
SearchClause::Field(f, op, v) => {
let tv = &f.get_value(t);
op.matches(tv, v)
}
SearchClause::And(clauses) => {
let mut res = true;
for c in clauses {
if !c.matches(t) {
res = false;
break;
}
}
res
}
SearchClause::Or(clauses) => {
let mut res = false;
for c in clauses {
if c.matches(t) {
res = true;
break;
}
}
res
}
}
}
}
#[derive(Eq, PartialEq, Debug)]
struct Where {
search_clause: SearchClause,
}
impl Where {
fn matches(&self, t: &Todo) -> bool {
self.search_clause.matches(t)
}
}
#[derive(Eq, PartialEq, Debug)]
struct Group {
by: Field,
}
#[derive(Eq, PartialEq, Hash, Debug)]
enum Function {
ArrayLength,
}
#[derive(Eq, PartialEq, Debug)]
struct Order {
by: Field,
desc: bool,
}
#[derive(Default, TypedBuilder, Eq, PartialEq, Debug)]
struct Statement {
#[builder(default)]
select: Select,
#[builder(default, setter(strip_option))]
where_: Option<Where>,
#[builder(default, setter(strip_option))]
group: Option<Group>,
#[builder(default, setter(strip_option))]
order: Option<Order>,
}
impl Statement {
fn matches(&self, t: &Todo) -> bool {
match &self.where_ {
None => true,
Some(where_) => where_.matches(t),
}
}
}
#[cfg(test)]
mod test {
use simplelog::*;
use crate::sql::ComparisonOperator::*;
use crate::sql::Field::{
CompletionDate, CreationDate, IsCompleted, IsHidden, Priority, ThresholdDate,
};
use crate::sql::Value::{Date, False, Null, String_, True};
use crate::utils;
use super::*;
#[test]
fn matching_todos_with_empty_where() {
utils::test::init();
let t = Todo::parse("x (A) ma vai").unwrap();
let s = Statement::builder().build();
assert!(s.matches(&t))
}
#[test]
fn matching_todos_with_field_search_clauses() {
utils::test::init();
let mut t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();
let mut sc = SearchClause::Field(Priority, Equals, String_("A".to_string()));
assert!(sc.matches(&t));
sc = SearchClause::Field(Priority, Equals, String_("B".to_string()));
assert!(!sc.matches(&t));
sc = SearchClause::Field(IsCompleted, Equals, True);
assert!(sc.matches(&t));
sc = SearchClause::Field(CompletionDate, Equals, Null);
assert!(!sc.matches(&t));
sc = SearchClause::Field(CompletionDate, Is, Null);
assert!(sc.matches(&t));
sc = SearchClause::Field(CreationDate, Equals, Date(time::date!(2020 - 12 - 25)));
assert!(sc.matches(&t));
sc = SearchClause::Field(CreationDate, Equals, Date(time::date!(2020 - 12 - 31)));
assert!(!sc.matches(&t));
sc = SearchClause::Field(ThresholdDate, Equals, Date(time::date!(2020 - 12 - 30)));
assert!(!sc.matches(&t));
sc = SearchClause::Field(IsHidden, Equals, False);
assert!(sc.matches(&t));
}
#[test]
fn matching_todos_with_and_search_clauses() {
utils::test::init();
let mut t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();
let mut sc = SearchClause::And(vec![
SearchClause::Field(IsHidden, Equals, False),
SearchClause::Field(IsCompleted, Is, True),
]);
assert!(sc.matches(&t));
sc = SearchClause::And(vec![
SearchClause::Field(IsHidden, Equals, False),
SearchClause::Field(IsCompleted, Is, False),
]);
assert!(!sc.matches(&t));
}
#[test]
fn matching_todos_with_or_search_clauses() {
utils::test::init();
let mut t = Todo::parse("x (A) 2020-12-25 ma vai t:2020-12-30").unwrap();
let mut sc = SearchClause::Or(vec![
SearchClause::Field(IsCompleted, Equals, False),
SearchClause::Field(CreationDate, Is, Date(time::date!(2020 - 12 - 25))),
]);
assert!(sc.matches(&t));
sc = SearchClause::Or(vec![
SearchClause::Field(IsHidden, Equals, True),
SearchClause::Field(IsCompleted, Is, False),
]);
assert!(!sc.matches(&t));
}
#[test]
fn matching_todos_with_context_project_in_search_clauses() {}
}