graphrecords-query 0.5.0

High-performance graph-based data records
Documentation
use super::{ValueEquivalence, ValueOrdering, incomparable_with_first};
use crate::{
    AttributeName, Failure, IndexDomain, IndexValue, Mask, QueryResult, Scalar, ValueDomain,
    error::aggregation::InvalidMedianValue,
};
use chrono::TimeDelta;
use graphrecords_core::graphrecord::GraphRecordValue;

const NANOSECONDS_PER_SECOND: i128 = 1_000_000_000;

pub trait ValueMedian: ValueOrdering {
    fn validate_median(label: &'static str, value: &Self::Value<'_>) -> QueryResult<()>;

    fn find_incomparable_median_values<'a, 'b>(
        values: impl Iterator<Item = &'a Self::Value<'b>>,
    ) -> Option<(usize, usize)>
    where
        Self::Value<'b>: 'a;

    fn median<'a>(
        label: &'static str,
        lower: Self::Value<'a>,
        upper: Option<Self::Value<'a>>,
    ) -> QueryResult<Self::Value<'a>>;
}

pub trait ValueMode: ValueEquivalence {}

pub trait ValueScalar: ValueDomain {
    fn into_scalar(label: &'static str, value: Self::Value<'_>) -> QueryResult<GraphRecordValue>;

    fn from_scalar<'a>(role: &Self::Value<'_>, value: GraphRecordValue) -> Self::Value<'a>;
}

fn validate_graphrecord_median_value(
    label: &'static str,
    value: &GraphRecordValue,
) -> QueryResult<()> {
    if matches!(
        value,
        GraphRecordValue::Int(_)
            | GraphRecordValue::Float(_)
            | GraphRecordValue::DateTime(_)
            | GraphRecordValue::Duration(_)
    ) {
        Ok(())
    } else {
        Err(Failure::new(label, InvalidMedianValue::new(value.clone())))
    }
}

fn median_graphrecord_value(
    lower: GraphRecordValue,
    upper: Option<GraphRecordValue>,
) -> GraphRecordValue {
    match (lower, upper) {
        (GraphRecordValue::Int(value), None) => GraphRecordValue::Float(value as f64),
        (GraphRecordValue::Float(value), None) => GraphRecordValue::Float(value),
        (GraphRecordValue::DateTime(value), None) => GraphRecordValue::DateTime(value),
        (GraphRecordValue::Duration(value), None) => GraphRecordValue::Duration(value),
        (GraphRecordValue::Int(lower), Some(GraphRecordValue::Int(upper))) => {
            GraphRecordValue::Float((lower as f64).midpoint(upper as f64))
        }
        (GraphRecordValue::Int(lower), Some(GraphRecordValue::Float(upper))) => {
            GraphRecordValue::Float((lower as f64).midpoint(upper))
        }
        (GraphRecordValue::Float(lower), Some(GraphRecordValue::Int(upper))) => {
            GraphRecordValue::Float(lower.midpoint(upper as f64))
        }
        (GraphRecordValue::Float(lower), Some(GraphRecordValue::Float(upper))) => {
            GraphRecordValue::Float(lower.midpoint(upper))
        }
        (GraphRecordValue::DateTime(lower), Some(GraphRecordValue::DateTime(upper))) => {
            let difference = upper.signed_duration_since(lower);
            let half = difference.checked_div(2).expect("two is a nonzero divisor");

            GraphRecordValue::DateTime(
                lower
                    .checked_add_signed(half)
                    .expect("a datetime midpoint lies between its inputs"),
            )
        }
        (GraphRecordValue::Duration(lower), Some(GraphRecordValue::Duration(upper))) => {
            let lower = i128::from(lower.num_seconds()) * NANOSECONDS_PER_SECOND
                + i128::from(lower.subsec_nanos());
            let upper = i128::from(upper.num_seconds()) * NANOSECONDS_PER_SECOND
                + i128::from(upper.subsec_nanos());
            let midpoint = lower
                .checked_add(upper)
                .expect("two durations fit within i128 nanoseconds")
                / 2;
            let seconds = midpoint.div_euclid(NANOSECONDS_PER_SECOND);
            let nanoseconds = midpoint.rem_euclid(NANOSECONDS_PER_SECOND);

            GraphRecordValue::Duration(
                TimeDelta::new(
                    i64::try_from(seconds).expect("a duration midpoint fits in i64 seconds"),
                    u32::try_from(nanoseconds).expect("subsecond nanoseconds fit in a u32"),
                )
                .expect("a duration midpoint lies between its inputs"),
            )
        }
        _ => unreachable!("median values were validated and checked for comparability"),
    }
}

impl ValueMedian for Scalar {
    fn validate_median(label: &'static str, value: &Self::Value<'_>) -> QueryResult<()> {
        validate_graphrecord_median_value(label, value)
    }

    fn find_incomparable_median_values<'a, 'b>(
        values: impl Iterator<Item = &'a Self::Value<'b>>,
    ) -> Option<(usize, usize)>
    where
        Self::Value<'b>: 'a,
    {
        incomparable_with_first(values)
    }

    fn median<'a>(
        _label: &'static str,
        lower: Self::Value<'a>,
        upper: Option<Self::Value<'a>>,
    ) -> QueryResult<Self::Value<'a>> {
        Ok(median_graphrecord_value(lower, upper))
    }
}

impl ValueScalar for Scalar {
    fn into_scalar(_label: &'static str, value: Self::Value<'_>) -> QueryResult<GraphRecordValue> {
        Ok(value)
    }

    fn from_scalar<'a>(_role: &Self::Value<'_>, value: GraphRecordValue) -> Self::Value<'a> {
        value
    }
}

impl ValueMode for Scalar {}

impl ValueMode for Mask {}

impl ValueMode for AttributeName {}

impl ValueMedian for IndexValue<GraphRecordValue> {
    fn validate_median(label: &'static str, value: &Self::Value<'_>) -> QueryResult<()> {
        validate_graphrecord_median_value(label, value)
    }

    fn find_incomparable_median_values<'a, 'b>(
        values: impl Iterator<Item = &'a Self::Value<'b>>,
    ) -> Option<(usize, usize)>
    where
        Self::Value<'b>: 'a,
    {
        incomparable_with_first(values)
    }

    fn median<'a>(
        _label: &'static str,
        lower: Self::Value<'a>,
        upper: Option<Self::Value<'a>>,
    ) -> QueryResult<Self::Value<'a>> {
        Ok(median_graphrecord_value(lower, upper))
    }
}

impl ValueScalar for IndexValue<GraphRecordValue> {
    fn into_scalar(_label: &'static str, value: Self::Value<'_>) -> QueryResult<GraphRecordValue> {
        Ok(value)
    }

    fn from_scalar<'a>(_role: &Self::Value<'_>, value: GraphRecordValue) -> Self::Value<'a> {
        value
    }
}

impl<I: IndexDomain> ValueMode for IndexValue<I> {}