mem-query 0.0.1

Relational algebra interface for Rust collections
use {
    crate::{
        col::{ColProxy,Col},
        rename_col::RenameCol,
        header::{Header},
        record::Record,
    },
    tylisp::{
        HCons, HNil, sexpr, defun_nocalc, 
        non_calc_literal,
        engine::Eval,
        ops::{
            list::{Union, SupersetP, EmptyP, Tail},
        },
        marker_traits::List
    },
};

use std::ops::{Bound,RangeBounds};
use std::any::Any;

pub trait QueryFilter: Clone {
    type ReqCols: Header;
    fn test_record(&self, _:&impl Record)->bool;
    fn bounds_of<C:Col+Ord>(&self)->(Bound<&'_ C>, Bound<&'_ C>) {
        (Bound::Unbounded, Bound::Unbounded)
    }
}

pub trait IntoFilter {
    type Result:QueryFilter + List;
    fn into_filter(self)->Self::Result;
}

impl IntoFilter for HNil {
    type Result = Self;
    fn into_filter(self)->Self { self }
}

impl<H:ColProxy+PartialEq, T:IntoFilter> IntoFilter for HCons<H,T>
where HCons<Exact<H>, T::Result>: QueryFilter {
    type Result = HCons<Exact<H>, T::Result>;
    fn into_filter(self)->Self::Result {
        HCons { head: Exact(self.head), tail: self.tail.into_filter() }
    }
}

#[derive(Copy,Clone,Default)]
pub struct FilterForHeader;
defun_nocalc!{() FilterForHeader {
    (H: Header, F:QueryFilter) { _:H, _:F } =>
        {SupersetP, @H, @F::ReqCols};
}}

#[derive(Copy,Clone,Default)]
pub struct SingleColFilter;
defun_nocalc!{() SingleColFilter {
    (F:QueryFilter) {_:F} => {EmptyP, {Tail, @F::ReqCols}};
}}

#[test] fn test_filter_for_header() {
    col!{A: usize}
    col!{B: usize}
    col!{C: usize}
    use tylisp::{typenum as tn, eval, ops::list::BuildList};
    assert_type_eq! { tn::True : eval!{FilterForHeader, @{A,B}, @Exact<A>}}
    assert_type_eq! { tn::True : eval!{FilterForHeader, @{A,B}, @Exact<B>}}
    assert_type_eq! { tn::False: eval!{FilterForHeader, @{A,B}, @Exact<C>}}

    assert_type_eq! { tn::True : eval!{FilterForHeader, @{A,B}, @HNil}}

    assert_type_eq! { tn::True : eval!{FilterForHeader, @{A,B}, {BuildList, @Exact<A>, @Exact<B>}}}
    assert_type_eq! { tn::True : eval!{FilterForHeader, @{A,B}, {BuildList, @Exact<B>, @Exact<A>}}}
    assert_type_eq! { tn::False: eval!{FilterForHeader, @{A,B}, {BuildList, @Exact<C>, @Exact<A>}}}
    assert_type_eq! { tn::False: eval!{FilterForHeader, @{A,B}, {BuildList, @Exact<A>, @Exact<C>}}}
}

#[derive(Copy, Clone, Debug)]
pub struct Exact<T>(pub T);
non_calc_literal!({T} Exact<T>);

impl<A,B,T> RenameCol<A,B> for Exact<T>
where A: Col, B:Col<Inner = A::Inner>,
    T: RenameCol<A,B>,
    Exact<T::Renamed>: QueryFilter {
    type Renamed = Exact<T::Renamed>;
    fn rename_col(self)->Exact<T::Renamed> { Exact(self.0.rename_col()) }
}

impl<T:ColProxy+Clone> QueryFilter for Exact<T>
where T::For: PartialEq {
    type ReqCols = sexpr!{T::For};
    fn test_record(&self, rec:&impl Record)->bool {
        match rec.col_opt::<T::For>() {
            Some(c) => *c == *self.0.col_ref(),
            None => true
        }
    }
    fn bounds_of<C:Col+Ord>(&self)->(Bound<&'_ C>, Bound<&'_ C>) {
        let c: Option<&C> = (self.0.col_ref() as &dyn Any).downcast_ref();
        match c {
            Some(c) => (Bound::Included(c), Bound::Included(c)),
            None => (Bound::Unbounded, Bound::Unbounded)
        }
    }
}

impl QueryFilter for HNil {
    type ReqCols = HNil;
    fn test_record(&self, _:&impl Record)->bool { true }
    fn bounds_of<C:Col+Ord>(&self)->(Bound<&'_ C>, Bound<&'_ C>) {
        (Bound::Unbounded, Bound::Unbounded)
    }
}

impl<H,T, UnionCols> QueryFilter for HCons<H,T> where
    H:QueryFilter,
    T:QueryFilter,
    sexpr!{Union, @H::ReqCols, @T::ReqCols}: Eval<Result=UnionCols>,
    UnionCols: Header,
{
    type ReqCols = UnionCols;

    #[inline(always)]
    fn test_record(&self, rec:&impl Record)->bool {
        self.head.test_record(rec) && self.tail.test_record(rec)
    }
    fn bounds_of<C:Col+Ord>(&self)->(Bound<&'_ C>, Bound<&'_ C>) {
        let (lo_1, hi_1) = self.head.bounds_of::<C>();
        let (lo_2, hi_2) = self.tail.bounds_of::<C>();
        
        use Bound::*;
        let lo = match (lo_1, lo_2) {
            (Unbounded, _) => lo_2,
            (_, Unbounded) => lo_1,
            (Included(a), Included(b)) => Included(a.max(b)),
            (Excluded(a), Excluded(b)) => Excluded(a.max(b)),
            (Included(a), Excluded(b)) => if a > b { Included(a) }
                                          else     { Excluded(b) },
            (Excluded(a), Included(b)) => if b > a { Included(b) }
                                          else     { Excluded(a) },
        };

        let hi = match (hi_1, hi_2) {
            (Unbounded, _) => hi_2,
            (_, Unbounded) => hi_1,
            (Included(a), Included(b)) => Included(a.min(b)),
            (Excluded(a), Excluded(b)) => Excluded(a.min(b)),
            (Included(a), Excluded(b)) => if a < b { Included(a) }
                                          else     { Excluded(b) },
            (Excluded(a), Included(b)) => if b < a { Included(b) }
                                          else     { Excluded(a) },
        };

        (lo, hi)
    }
}