graphrecords-query 0.5.0

High-performance graph-based data records
Documentation
use super::reject_key_failures;
use crate::{
    Arity, Bare, BareValueDomain, Definite, EvaluateOperand, Explain, Failure, IndexDomain,
    Indexed, Labeled, Operand, QueryResult, Single, ValueDomain,
    capabilities::GroupingValue,
    error::grouping::MissingGroupAggregate,
    execution::EvaluationCache,
    index::GroupKey,
    operands::{OperandHandle, Partition},
    operations::{
        Apply, GroupKernel, IndexedElementContainer, IndexedElementSource, Operation,
        OperationContext, Prepare,
    },
    optimizer::{
        Estimate, Estimated, OperationInputs, OptimizerHints, PlanIdentity, PlanInputs, Stats,
    },
    registry::operation_manifest,
    traits::BroadcastVia,
};
use graphrecords_core::GraphRecord;
use graphrecords_utils::aliases::GrHashMap;

#[derive(Clone, Explain, Operation, OperationInputs, OptimizerHints, PlanIdentity, PlanInputs)]
#[operation(scope = Group)]
#[explain(label = "BroadcastVia")]
#[plan(optimizer_hints(empty = if_all))]
pub struct BroadcastViaOperation<A> {
    #[argument]
    via: A,
}

impl<A: Prepare> Prepare for BroadcastViaOperation<A> {
    type Prepared<'a>
        = A::Prepared<'a>
    where
        Self: 'a;

    fn prepare<'a>(
        &'a self,
        graphrecord: &'a GraphRecord,
        cache: &'a EvaluationCache<'a>,
    ) -> QueryResult<Self::Prepared<'a>> {
        self.via.prepare(graphrecord, cache)
    }
}

fn broadcast_via<'a, K, V, A>(
    prepared: A::Prepared<'a>,
    aggregates: GrHashMap<K::Owned, Option<QueryResult<V::Value<'a>>>>,
    label: &'static str,
) -> IndexedElementContainer<'a, A::IndexDomain, V::Value<'a>, A::Arity>
where
    K: GroupKey,
    V: ValueDomain,
    A: IndexedElementSource + 'a,
    A::ValueDomain: GroupingValue<Key = K>,
{
    let elements = A::elements(prepared);

    A::Arity::map_elements(elements, move |(index, via_outcome)| {
        let outcome = match via_outcome {
            Err(failure) => Err(failure),
            Ok(value) => match aggregates.get(&A::ValueDomain::to_group_key(&value)) {
                Some(Some(aggregate)) => aggregate.clone(),
                Some(None) | None => Err(Failure::new_at::<A::IndexDomain, _>(
                    label,
                    MissingGroupAggregate,
                    &index,
                )),
            },
        };

        (index, outcome)
    })
}

fn broadcast_via_estimate<A: Estimated>(
    operation: &BroadcastViaOperation<A>,
    input: &Estimate,
    stats: &Stats,
) -> Estimate {
    let via = operation.via.estimate(stats);

    Estimate {
        elements: via.elements,
        distinct: input.elements,
        selectivity: via.selectivity,
        per_group: None,
    }
}

impl<M, K, J, V, A> GroupKernel<M, K, OperandHandle<Indexed<J, V>, Single>>
    for BroadcastViaOperation<A>
where
    M: IndexDomain,
    K: GroupKey,
    J: IndexDomain,
    V: ValueDomain,
    A: IndexedElementSource,
    A::ValueDomain: GroupingValue<Key = K>,
{
    type Output = OperandHandle<Indexed<A::IndexDomain, V>, A::Arity>;

    fn execute<'a>(
        _graphrecord: &'a GraphRecord,
        partition: Partition<'a, M, K, OperandHandle<Indexed<J, V>, Single>>,
        prepared: Self::Prepared<'a>,
    ) -> QueryResult<<Self::Output as EvaluateOperand>::ReturnValue<'a>> {
        let (buckets, key_failures) = partition.into_parts();
        reject_key_failures::<M>(key_failures, Self::LABEL)?;

        let aggregates = buckets
            .into_iter()
            .map(|(key, _, payload)| {
                let aggregate = match payload {
                    Ok(Some((_, outcome))) => Some(outcome),
                    Ok(None) => None,
                    Err(failure) => Some(Err(failure)),
                };

                (key, aggregate)
            })
            .collect();

        Ok(broadcast_via::<K, V, A>(prepared, aggregates, Self::LABEL))
    }

    fn estimate(&self, input: Estimate, stats: &Stats) -> Estimate {
        broadcast_via_estimate(self, &input, stats)
    }
}

impl<M, K, V, A> GroupKernel<M, K, OperandHandle<Bare<V>, Single>> for BroadcastViaOperation<A>
where
    M: IndexDomain,
    K: GroupKey,
    V: BareValueDomain,
    A: IndexedElementSource,
    A::ValueDomain: GroupingValue<Key = K>,
{
    type Output = OperandHandle<Indexed<A::IndexDomain, V>, A::Arity>;

    fn execute<'a>(
        _graphrecord: &'a GraphRecord,
        partition: Partition<'a, M, K, OperandHandle<Bare<V>, Single>>,
        prepared: Self::Prepared<'a>,
    ) -> QueryResult<<Self::Output as EvaluateOperand>::ReturnValue<'a>> {
        let (buckets, key_failures) = partition.into_parts();
        reject_key_failures::<M>(key_failures, Self::LABEL)?;

        let aggregates = buckets
            .into_iter()
            .map(|(key, _, payload)| {
                let aggregate = match payload {
                    Ok(Some(outcome)) => Some(outcome),
                    Ok(None) => None,
                    Err(failure) => Some(Err(failure)),
                };

                (key, aggregate)
            })
            .collect();

        Ok(broadcast_via::<K, V, A>(prepared, aggregates, Self::LABEL))
    }

    fn estimate(&self, input: Estimate, stats: &Stats) -> Estimate {
        broadcast_via_estimate(self, &input, stats)
    }
}

impl<M, K, J, V, A> GroupKernel<M, K, OperandHandle<Indexed<J, V>, Definite>>
    for BroadcastViaOperation<A>
where
    M: IndexDomain,
    K: GroupKey,
    J: IndexDomain,
    V: ValueDomain,
    A: IndexedElementSource,
    A::ValueDomain: GroupingValue<Key = K>,
{
    type Output = OperandHandle<Indexed<A::IndexDomain, V>, A::Arity>;

    fn execute<'a>(
        _graphrecord: &'a GraphRecord,
        partition: Partition<'a, M, K, OperandHandle<Indexed<J, V>, Definite>>,
        prepared: Self::Prepared<'a>,
    ) -> QueryResult<<Self::Output as EvaluateOperand>::ReturnValue<'a>> {
        let (buckets, key_failures) = partition.into_parts();
        reject_key_failures::<M>(key_failures, Self::LABEL)?;

        let aggregates = buckets
            .into_iter()
            .map(|(key, _, payload)| {
                let aggregate = match payload {
                    Ok((_, outcome)) => outcome,
                    Err(failure) => Err(failure),
                };

                (key, Some(aggregate))
            })
            .collect();

        Ok(broadcast_via::<K, V, A>(prepared, aggregates, Self::LABEL))
    }

    fn estimate(&self, input: Estimate, stats: &Stats) -> Estimate {
        broadcast_via_estimate(self, &input, stats)
    }
}

impl<M, K, V, A> GroupKernel<M, K, OperandHandle<Bare<V>, Definite>> for BroadcastViaOperation<A>
where
    M: IndexDomain,
    K: GroupKey,
    V: BareValueDomain,
    A: IndexedElementSource,
    A::ValueDomain: GroupingValue<Key = K>,
{
    type Output = OperandHandle<Indexed<A::IndexDomain, V>, A::Arity>;

    fn execute<'a>(
        _graphrecord: &'a GraphRecord,
        partition: Partition<'a, M, K, OperandHandle<Bare<V>, Definite>>,
        prepared: Self::Prepared<'a>,
    ) -> QueryResult<<Self::Output as EvaluateOperand>::ReturnValue<'a>> {
        let (buckets, key_failures) = partition.into_parts();
        reject_key_failures::<M>(key_failures, Self::LABEL)?;

        let aggregates = buckets
            .into_iter()
            .map(|(key, _, payload)| {
                let aggregate = match payload {
                    Ok(outcome) => outcome,
                    Err(failure) => Err(failure),
                };

                (key, Some(aggregate))
            })
            .collect();

        Ok(broadcast_via::<K, V, A>(prepared, aggregates, Self::LABEL))
    }

    fn estimate(&self, input: Estimate, stats: &Stats) -> Estimate {
        broadcast_via_estimate(self, &input, stats)
    }
}

impl<O, A> BroadcastVia<A::IndexDomain, A> for O
where
    A: IndexedElementSource,
    BroadcastViaOperation<A>: Operation,
    O: Apply<BroadcastViaOperation<A>>,
{
    type ReturnOperand = O::Output;

    fn broadcast_via(&self, via: A) -> Self::ReturnOperand {
        Self::ReturnOperand::new(OperationContext::new(
            self.clone(),
            BroadcastViaOperation { via },
        ))
    }
}

operation_manifest! {
    BroadcastViaOperation<A> {
        method: BroadcastVia<J, A>::broadcast_via;
        scope: group;

        kernel {
            group: <M: IndexDomain, K: GroupKey>;
            parameters: <
                P: IndexDomain,
                V: ValueDomain,
                J: IndexDomain,
                X: GroupingValue<K>,
                C: EnumerableArity,
            >;
            argument: A: IndexedElementSource<Indexed<J, X>, C>;
            input: OperandHandle<Indexed<P, V>, Single>;
            output: OperandHandle<Indexed<J, V>, C>;
        }

        kernel {
            group: <M: IndexDomain, K: GroupKey>;
            parameters: <
                P: IndexDomain,
                V: ValueDomain,
                J: IndexDomain,
                X: GroupingValue<K>,
                C: EnumerableArity,
            >;
            argument: A: IndexedElementSource<Indexed<J, X>, C>;
            input: OperandHandle<Indexed<P, V>, Definite>;
            output: OperandHandle<Indexed<J, V>, C>;
        }

        kernel {
            group: <M: IndexDomain, K: GroupKey>;
            parameters: <
                V: BareValueDomain,
                J: IndexDomain,
                X: GroupingValue<K>,
                C: EnumerableArity,
            >;
            argument: A: IndexedElementSource<Indexed<J, X>, C>;
            input: OperandHandle<Bare<V>, Single>;
            output: OperandHandle<Indexed<J, V>, C>;
        }

        kernel {
            group: <M: IndexDomain, K: GroupKey>;
            parameters: <
                V: BareValueDomain,
                J: IndexDomain,
                X: GroupingValue<K>,
                C: EnumerableArity,
            >;
            argument: A: IndexedElementSource<Indexed<J, X>, C>;
            input: OperandHandle<Bare<V>, Definite>;
            output: OperandHandle<Indexed<J, V>, C>;
        }
    }
}