selene-db-gql 1.3.0

ISO/IEC 39075:2024 GQL parser, planner, optimizer, and executor for selene-db.
Documentation
//! Evaluation for ISO `PATH[...]` value construction.

use selene_core::{EdgeDirection, Path, PathSegment, Value};
use smallvec::SmallVec;

use crate::{
    SourceSpan, ValueExpr,
    runtime::{Binding, BindingTableSchema, DataExceptionSubclass, EvalCtx, ExecutorError},
};

use super::evaluate;

pub(super) fn eval_path_constructor(
    elements: &[ValueExpr],
    span: SourceSpan,
    binding: &Binding,
    schema: &BindingTableSchema,
    ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<Value, ExecutorError> {
    let values = elements
        .iter()
        .map(|element| evaluate(element, binding, schema, ctx))
        .collect::<Result<Vec<_>, _>>()?;
    construct_path(values, span, ctx)
}

fn construct_path(
    values: Vec<Value>,
    span: SourceSpan,
    ctx: &EvalCtx<'_, '_, '_, '_>,
) -> Result<Value, ExecutorError> {
    if values.is_empty() || values.len().is_multiple_of(2) {
        return malformed_path(
            "PATH constructor requires node, edge, node, ... elements",
            span,
        );
    }
    let Some(Value::NodeRef(start)) = values.first().cloned() else {
        return malformed_path(
            "PATH constructor must start with a live node reference",
            span,
        );
    };
    if ctx.tx.snapshot().node_labels(start).is_none() {
        return malformed_path("PATH constructor start node is not live", span);
    }
    let mut current = start;
    let mut segments = SmallVec::<[PathSegment; 4]>::new();
    for pair in values[1..].chunks_exact(2) {
        let (Value::EdgeRef(edge), Value::NodeRef(node)) = (&pair[0], &pair[1]) else {
            return malformed_path("PATH constructor elements must alternate edge, node", span);
        };
        if ctx.tx.snapshot().node_labels(*node).is_none() {
            return malformed_path("PATH constructor target node is not live", span);
        }
        let Some((source, target)) = ctx.tx.snapshot().edge_endpoints(*edge) else {
            return malformed_path("PATH constructor edge is not live", span);
        };
        let direction = if source == current && target == *node {
            EdgeDirection::Outgoing
        } else if target == current && source == *node {
            EdgeDirection::Incoming
        } else {
            return malformed_path("PATH constructor elements do not identify a path", span);
        };
        segments.push(PathSegment {
            edge: *edge,
            direction,
            node: *node,
        });
        current = *node;
    }
    Ok(Value::Path(Box::new(Path {
        graph: ctx.tx.snapshot().graph_id(),
        start,
        segments,
    })))
}

fn malformed_path<T>(message: &'static str, span: SourceSpan) -> Result<T, ExecutorError> {
    Err(ExecutorError::data_exception(
        DataExceptionSubclass::MalformedPath,
        message,
        span,
    ))
}