graphrecords-query 0.5.0

High-performance graph-based data records
Documentation
use super::{
    plan::{MatchInputs, OptimizePlan, PlanNode},
    rule::{Rule, Transformed},
    stats::Stats,
};
use crate::Operand;
use std::marker::PhantomData;

pub trait Pattern<O: Operand> {
    type Bindings;

    fn try_match(&self, operand: &O) -> Option<Self::Bindings>;

    fn rewrite<F>(self, rewrite: F) -> impl Rule<O>
    where
        Self: Sized + Send + Sync + 'static,
        O: 'static,
        F: Fn(Self::Bindings, &Stats) -> Option<O> + Send + Sync + 'static,
    {
        PatternRule {
            pattern: self,
            rewrite,
        }
    }

    fn guard<G>(self, guard: G) -> GuardedPattern<Self, G>
    where
        Self: Sized,
        G: Fn(&Stats) -> bool,
    {
        GuardedPattern {
            pattern: self,
            guard,
        }
    }

    fn or<Q>(self, other: Q) -> impl Pattern<O, Bindings = Self::Bindings>
    where
        Self: Sized,
        Q: Pattern<O, Bindings = Self::Bindings>,
    {
        OrPattern {
            left: self,
            right: other,
        }
    }
}

struct PatternRule<P, F> {
    pattern: P,
    rewrite: F,
}

impl<O, P, F> Rule<O> for PatternRule<P, F>
where
    O: Operand,
    P: Pattern<O> + Send + Sync + 'static,
    F: Fn(P::Bindings, &Stats) -> Option<O> + Send + Sync + 'static,
{
    fn apply(&self, operand: O, stats: &Stats) -> Transformed<O> {
        let Some(bindings) = self.pattern.try_match(&operand) else {
            return Transformed::unchanged(operand);
        };

        match (self.rewrite)(bindings, stats) {
            Some(rewritten) => Transformed::changed(rewritten),
            None => Transformed::unchanged(operand),
        }
    }
}

pub struct GuardedPattern<P, G> {
    pattern: P,
    guard: G,
}

impl<P, G> GuardedPattern<P, G> {
    pub fn rewrite<O, F>(self, rewrite: F) -> impl Rule<O>
    where
        O: Operand + 'static,
        P: Pattern<O> + Send + Sync + 'static,
        G: Fn(&Stats) -> bool + Send + Sync + 'static,
        F: Fn(P::Bindings, &Stats) -> Option<O> + Send + Sync + 'static,
    {
        let guard = self.guard;

        PatternRule {
            pattern: self.pattern,
            rewrite: move |bindings, stats: &Stats| {
                if guard(stats) {
                    rewrite(bindings, stats)
                } else {
                    None
                }
            },
        }
    }
}

struct OrPattern<P, Q> {
    left: P,
    right: Q,
}

impl<O: Operand, P: Pattern<O>, Q: Pattern<O, Bindings = P::Bindings>> Pattern<O>
    for OrPattern<P, Q>
{
    type Bindings = P::Bindings;

    fn try_match(&self, operand: &O) -> Option<Self::Bindings> {
        self.left
            .try_match(operand)
            .or_else(|| self.right.try_match(operand))
    }
}

pub struct NotPattern<P> {
    inner: P,
}

#[must_use]
pub const fn not<P>(inner: P) -> NotPattern<P> {
    NotPattern { inner }
}

impl<O: Operand, P: Pattern<O>> Pattern<O> for NotPattern<P> {
    type Bindings = ();

    fn try_match(&self, operand: &O) -> Option<Self::Bindings> {
        self.inner.try_match(operand).is_none().then_some(())
    }
}

pub struct Wildcard;

#[must_use]
pub const fn any() -> Wildcard {
    Wildcard
}

impl<O: Operand> Pattern<O> for Wildcard {
    type Bindings = ();

    fn try_match(&self, _operand: &O) -> Option<Self::Bindings> {
        Some(())
    }
}

pub struct Capture;

#[must_use]
pub const fn capture() -> Capture {
    Capture
}

impl<O: Operand> Pattern<O> for Capture {
    type Bindings = O;

    fn try_match(&self, operand: &O) -> Option<Self::Bindings> {
        Some(operand.clone())
    }
}

pub struct Matching<C, P> {
    patterns: P,
    matched: PhantomData<fn() -> C>,
}

#[must_use]
pub const fn matching<C, P>(patterns: P) -> Matching<C, P> {
    Matching {
        patterns,
        matched: PhantomData,
    }
}

impl<C, P, B> Pattern<C::Output> for Matching<C, P>
where
    C: PlanNode + MatchInputs + OptimizePlan,
    P: for<'a> MatchAgainst<C::Inputs<'a>, Bindings = B>,
{
    type Bindings = B;

    fn try_match(&self, operand: &C::Output) -> Option<Self::Bindings> {
        let context = operand.as_plan_node().downcast::<C>()?;

        self.patterns.match_against(MatchInputs::inputs(context))
    }
}

impl<C, P> Matching<C, P> {
    pub fn rewrite_matched<B, F>(self, rewrite: F) -> impl Rule<C::Output>
    where
        C: PlanNode + MatchInputs + OptimizePlan,
        P: for<'a> MatchAgainst<C::Inputs<'a>, Bindings = B> + Send + Sync + 'static,
        B: 'static,
        F: Fn(&C, B, &Stats) -> Option<C::Output> + Send + Sync + 'static,
    {
        MatchingRewriteRule {
            pattern: self,
            rewrite,
        }
    }
}

struct MatchingRewriteRule<C, P, F> {
    pattern: Matching<C, P>,
    rewrite: F,
}

impl<C, P, B, F> Rule<C::Output> for MatchingRewriteRule<C, P, F>
where
    C: PlanNode + MatchInputs + OptimizePlan,
    P: for<'a> MatchAgainst<C::Inputs<'a>, Bindings = B> + Send + Sync + 'static,
    B: 'static,
    F: Fn(&C, B, &Stats) -> Option<C::Output> + Send + Sync + 'static,
{
    fn apply(&self, operand: C::Output, stats: &Stats) -> Transformed<C::Output> {
        let Some(context) = operand.as_plan_node().downcast::<C>() else {
            return Transformed::unchanged(operand);
        };

        let Some(bindings) = self
            .pattern
            .patterns
            .match_against(MatchInputs::inputs(context))
        else {
            return Transformed::unchanged(operand);
        };

        match (self.rewrite)(context, bindings, stats) {
            Some(rewritten) => Transformed::changed(rewritten),
            None => Transformed::unchanged(operand),
        }
    }
}

pub trait MatchAgainst<I> {
    type Bindings;

    fn match_against(&self, inputs: I) -> Option<Self::Bindings>;
}

impl MatchAgainst<()> for () {
    type Bindings = ();

    fn match_against(&self, _inputs: ()) -> Option<Self::Bindings> {
        Some(())
    }
}

macro_rules! impl_match_against {
    ($($index:tt $operand:ident $pattern:ident),+) => {
        impl<'inputs, $($operand,)+ $($pattern,)+> MatchAgainst<($(&'inputs $operand,)+)>
            for ($($pattern,)+)
        where
            $($operand: Operand,)+
            $($pattern: Pattern<$operand>,)+
        {
            type Bindings = ($(<$pattern as Pattern<$operand>>::Bindings,)+);

            fn match_against(&self, inputs: ($(&$operand,)+)) -> Option<Self::Bindings> {
                Some(($( self.$index.try_match(inputs.$index)?, )+))
            }
        }
    };
}

impl_match_against!(0 O0 P0);
impl_match_against!(0 O0 P0, 1 O1 P1);
impl_match_against!(0 O0 P0, 1 O1 P1, 2 O2 P2);
impl_match_against!(0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3);
impl_match_against!(0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4);
impl_match_against!(0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4, 5 O5 P5);
impl_match_against!(0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4, 5 O5 P5, 6 O6 P6);
impl_match_against!(0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4, 5 O5 P5, 6 O6 P6, 7 O7 P7);
impl_match_against!(
    0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4, 5 O5 P5, 6 O6 P6, 7 O7 P7, 8 O8 P8
);
impl_match_against!(
    0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4, 5 O5 P5, 6 O6 P6, 7 O7 P7, 8 O8 P8, 9 O9 P9
);
impl_match_against!(
    0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4, 5 O5 P5, 6 O6 P6, 7 O7 P7, 8 O8 P8, 9 O9 P9,
    10 O10 P10
);
impl_match_against!(
    0 O0 P0, 1 O1 P1, 2 O2 P2, 3 O3 P3, 4 O4 P4, 5 O5 P5, 6 O6 P6, 7 O7 P7, 8 O8 P8, 9 O9 P9,
    10 O10 P10, 11 O11 P11
);