graphrecords-query 0.5.0

High-performance graph-based data records
Documentation
use crate::{
    Arity, Bare, BareValueDomain, Definite, ElementShape, IndexDomain, Indexed, Multiple, Operand,
    OrderState, QueryResult, Single, ValueDomain, operands::OperandHandle,
};
use elsa::FrozenMap;
use graphrecords_core::GraphRecord;
use std::{
    any::Any,
    hash::{Hash, Hasher},
    ptr,
    sync::Arc,
};

pub trait CacheableShape: ElementShape {
    type CachedElement: 'static;

    fn into_cached_element(element: Self::Element<'_>) -> Self::CachedElement;

    fn from_cached_element(cached: &Self::CachedElement) -> Self::Element<'_>;
}

pub trait CacheableArity<S: CacheableShape>: Arity {
    type Cached: 'static;

    fn into_cached<'a>(values: Self::Container<'a, S::Element<'a>>) -> Self::Cached;

    fn from_cached(cached: &Self::Cached) -> Self::Container<'_, S::Element<'_>>;
}

pub trait CacheableOperand: Operand {
    type Cached: 'static;

    fn into_cached(values: Self::ReturnValue<'_>) -> Self::Cached;

    fn from_cached(cached: &Self::Cached) -> Self::ReturnValue<'_>;
}

impl<I: IndexDomain, V: ValueDomain> CacheableShape for Indexed<I, V> {
    type CachedElement = (I::Owned, QueryResult<V::Owned>);

    fn into_cached_element(element: Self::Element<'_>) -> Self::CachedElement {
        let (index, outcome) = element;

        (I::to_owned(&index), outcome.map(V::into_owned))
    }

    fn from_cached_element(cached: &Self::CachedElement) -> Self::Element<'_> {
        let outcome = match &cached.1 {
            Ok(value) => Ok(V::from_owned(value)),
            Err(failure) => Err(failure.clone()),
        };

        (I::from_owned(&cached.0), outcome)
    }
}

impl<V: BareValueDomain> CacheableShape for Bare<V> {
    type CachedElement = QueryResult<V::Owned>;

    fn into_cached_element(element: Self::Element<'_>) -> Self::CachedElement {
        element.map(V::into_owned)
    }

    fn from_cached_element(cached: &Self::CachedElement) -> Self::Element<'_> {
        match cached {
            Ok(value) => Ok(V::from_owned(value)),
            Err(failure) => Err(failure.clone()),
        }
    }
}

impl<S: CacheableShape> CacheableArity<S> for Definite {
    type Cached = S::CachedElement;

    fn into_cached<'a>(values: Self::Container<'a, S::Element<'a>>) -> Self::Cached {
        S::into_cached_element(values)
    }

    fn from_cached(cached: &Self::Cached) -> Self::Container<'_, S::Element<'_>> {
        S::from_cached_element(cached)
    }
}

impl<S: CacheableShape> CacheableArity<S> for Single {
    type Cached = Option<S::CachedElement>;

    fn into_cached<'a>(values: Self::Container<'a, S::Element<'a>>) -> Self::Cached {
        values.map(S::into_cached_element)
    }

    fn from_cached(cached: &Self::Cached) -> Self::Container<'_, S::Element<'_>> {
        cached.as_ref().map(S::from_cached_element)
    }
}

impl<S: CacheableShape, O: OrderState> CacheableArity<S> for Multiple<O> {
    type Cached = Vec<S::CachedElement>;

    fn into_cached<'a>(values: Self::Container<'a, S::Element<'a>>) -> Self::Cached {
        values.map(S::into_cached_element).collect()
    }

    fn from_cached(cached: &Self::Cached) -> Self::Container<'_, S::Element<'_>> {
        Box::new(cached.iter().map(S::from_cached_element))
    }
}

impl<S: CacheableShape, C: CacheableArity<S>> CacheableOperand for OperandHandle<S, C> {
    type Cached = C::Cached;

    fn into_cached(values: Self::ReturnValue<'_>) -> Self::Cached {
        C::into_cached(values)
    }

    fn from_cached(cached: &Self::Cached) -> Self::ReturnValue<'_> {
        C::from_cached(cached)
    }
}

#[derive(Clone)]
pub(crate) struct CacheSlot(Arc<CacheSlotMarker>);

struct CacheSlotMarker;

impl CacheSlot {
    #[must_use]
    pub(crate) fn new() -> Self {
        Self(Arc::new(CacheSlotMarker))
    }
}

impl PartialEq for CacheSlot {
    fn eq(&self, other: &Self) -> bool {
        Arc::ptr_eq(&self.0, &other.0)
    }
}

impl Eq for CacheSlot {}

impl Hash for CacheSlot {
    fn hash<H: Hasher>(&self, state: &mut H) {
        ptr::hash(Arc::as_ptr(&self.0), state);
    }
}

pub struct EvaluationCache<'a> {
    graphrecord: &'a GraphRecord,
    values: FrozenMap<CacheSlot, Box<dyn Any>>,
}

impl<'a> EvaluationCache<'a> {
    #[must_use]
    pub fn new(graphrecord: &'a GraphRecord) -> Self {
        Self {
            graphrecord,
            values: FrozenMap::new(),
        }
    }

    pub(crate) fn is_bound_to(&self, graphrecord: &GraphRecord) -> bool {
        ptr::eq(self.graphrecord, graphrecord)
    }

    pub(crate) fn materialize<O: CacheableOperand>(
        &'a self,
        slot: &CacheSlot,
        compute: impl FnOnce() -> QueryResult<O::ReturnValue<'a>>,
    ) -> QueryResult<O::ReturnValue<'a>> {
        let stored = if let Some(stored) = self.values.get(slot) {
            stored
        } else {
            let computed = Box::new(compute().map(O::into_cached));
            self.values.insert(slot.clone(), computed)
        };

        let stored = stored
            .downcast_ref::<QueryResult<O::Cached>>()
            .expect("Cache entry must match its slot's operand type");

        match stored {
            Ok(cached) => Ok(O::from_cached(cached)),
            Err(failure) => Err(failure.clone()),
        }
    }
}