use crate::executor::expression::evaluation_context::graph_storage::GraphStorageRef;
use crate::executor::expression::evaluator::expression_evaluator::ExpressionEvaluator;
use crate::executor::expression::functions::OwnedFunctionRef;
use crate::executor::expression::ExpressionError;
use crate::executor::streaming::slot::SlotId;
use linkrs_core::types::expr::{Expression, SubqueryBody};
use linkrs_core::value::list::List;
use linkrs_core::value::NullType;
use linkrs_core::vertex_edge_path::{Path, Step};
use linkrs_core::Value;
pub trait ExpressionContext {
fn get_variable(&self, name: &str) -> Option<Value>;
fn get_parameter(&self, _name: &str) -> Option<Value> {
None
}
fn get_session_variable(&self, name: &str) -> Result<Value, ExpressionError> {
Err(ExpressionError::type_error(format!(
"Session variable `{}` is not defined in this context",
name
)))
}
fn get_variable_by_slot(&self, _slot: SlotId) -> Option<Value> {
None
}
fn set_variable(&mut self, name: String, value: Value);
fn get_function(&self, name: &str) -> Option<OwnedFunctionRef> {
let _ = name;
None
}
fn get_graph_storage(&self) -> Option<GraphStorageRef> {
None
}
fn get_sequence_manager(
&self,
) -> Option<std::sync::Arc<linkrs_core::metadata::SequenceManager>> {
None
}
fn supports_cache(&self) -> bool {
false
}
fn get_cache(&mut self) -> Option<&mut ()> {
None
}
fn execute_subquery(&mut self, body: &SubqueryBody) -> Result<Vec<Value>, ExpressionError> {
let _ = body;
Err(ExpressionError::type_error(
"Subquery execution not supported in this context",
))
}
fn execute_exists(&mut self, body: &SubqueryBody) -> Result<bool, ExpressionError> {
let results = self.execute_subquery(body)?;
Ok(!results.is_empty())
}
fn contains_subquery(
&mut self,
body: &SubqueryBody,
value: &Value,
) -> Result<Value, ExpressionError> {
if value.is_null() {
return Ok(Value::Bool(false));
}
let results = self.execute_subquery(body)?;
Ok(Value::Bool(
results.iter().any(|v| !v.is_null() && v == value),
))
}
fn execute_scalar_subquery(&mut self, body: &SubqueryBody) -> Result<Value, ExpressionError> {
let _ = body;
Err(ExpressionError::type_error(
"Scalar subquery execution not supported in this context",
))
}
fn evaluate_label(&self, label: &str) -> Result<Value, ExpressionError> {
Err(ExpressionError::type_error(format!(
"Label expression `:{}` requires runtime context to resolve",
label
)))
}
fn evaluate_list_comprehension(
&mut self,
variable: &str,
source: &Expression,
filter: Option<&Expression>,
map: Option<&Expression>,
) -> Result<Value, ExpressionError>
where
Self: Sized,
{
let source_value = ExpressionEvaluator::evaluate(source, self)?;
let elements = match source_value {
Value::List(list) => list.values,
Value::Null(_) => return Ok(Value::Null(NullType::Null)),
other => {
return Err(ExpressionError::type_error(format!(
"List comprehension source must be a list, got {:?}",
other.get_type()
)))
}
};
let mut results = Vec::with_capacity(elements.len());
for element in elements {
self.set_variable(variable.to_string(), element);
if let Some(filter_expr) = filter {
match ExpressionEvaluator::evaluate(filter_expr, self)? {
Value::Bool(true) => {}
Value::Bool(false) | Value::Null(_) => continue,
other => {
return Err(ExpressionError::type_error(format!(
"List comprehension filter must evaluate to a boolean, got {:?}",
other.get_type()
)))
}
}
}
let mapped = match map {
Some(map_expr) => ExpressionEvaluator::evaluate(map_expr, self)?,
None => self
.get_variable(variable)
.unwrap_or(Value::Null(NullType::Null)),
};
results.push(mapped);
}
Ok(Value::list(List::from(results)))
}
fn evaluate_label_tag_property(
&mut self,
tag: &Expression,
property: &str,
) -> Result<Value, ExpressionError>
where
Self: Sized,
{
let tag_value = ExpressionEvaluator::evaluate(tag, self)?;
crate::executor::expression::evaluator::collection_operations::CollectionOperationEvaluator::eval_property_access(
&tag_value,
property,
)
}
fn evaluate_predicate(
&mut self,
func: &str,
args: &[Expression],
) -> Result<Value, ExpressionError>
where
Self: Sized,
{
let func_upper = func.to_uppercase();
let variable = match args.first() {
Some(Expression::Variable(name)) => name.clone(),
_ => {
return Err(ExpressionError::type_error(format!(
"Predicate `{}` requires a variable as its first argument",
func
)))
}
};
let source = args
.get(1)
.ok_or_else(|| ExpressionError::argument_count_error(3, args.len()))?;
let predicate = args
.get(2)
.ok_or_else(|| ExpressionError::argument_count_error(3, args.len()))?;
let source_value = ExpressionEvaluator::evaluate(source, self)?;
let elements = match source_value {
Value::List(list) => list.values,
Value::Null(_) => return Ok(Value::Null(NullType::Null)),
other => {
return Err(ExpressionError::type_error(format!(
"Predicate `{}` source must be a list, got {:?}",
func,
other.get_type()
)))
}
};
let mut matched = 0usize;
let total = elements.len();
for element in elements {
self.set_variable(variable.clone(), element);
match ExpressionEvaluator::evaluate(predicate, self)? {
Value::Bool(true) => matched += 1,
Value::Bool(false) | Value::Null(_) => {}
other => {
return Err(ExpressionError::type_error(format!(
"Predicate `{}` condition must evaluate to a boolean, got {:?}",
func,
other.get_type()
)))
}
}
}
match func_upper.as_str() {
"ALL" => Ok(Value::Bool(matched == total)),
"ANY" => Ok(Value::Bool(matched > 0)),
"SINGLE" => Ok(Value::Bool(matched == 1)),
"NONE" => Ok(Value::Bool(matched == 0)),
_ => Err(ExpressionError::type_error(format!(
"Unknown predicate function: {}",
func
))),
}
}
fn evaluate_reduce(
&mut self,
accumulator: &str,
initial: &Expression,
variable: &str,
source: &Expression,
mapping: &Expression,
) -> Result<Value, ExpressionError>
where
Self: Sized,
{
let mut acc = ExpressionEvaluator::evaluate(initial, self)?;
let source_value = ExpressionEvaluator::evaluate(source, self)?;
let elements = match source_value {
Value::List(list) => list.values,
Value::Null(_) => return Ok(Value::Null(NullType::Null)),
other => {
return Err(ExpressionError::type_error(format!(
"REDUCE source must be a list, got {:?}",
other.get_type()
)))
}
};
for element in elements {
self.set_variable(variable.to_string(), element);
self.set_variable(accumulator.to_string(), acc.clone());
acc = ExpressionEvaluator::evaluate(mapping, self)?;
}
Ok(acc)
}
fn evaluate_path_build(&mut self, items: &[Expression]) -> Result<Value, ExpressionError>
where
Self: Sized,
{
let mut iter = items.iter();
let src_value = match iter.next() {
Some(expr) => ExpressionEvaluator::evaluate(expr, self)?,
None => {
return Err(ExpressionError::path_error(
"Path construction requires at least one vertex",
))
}
};
let Value::Vertex(src) = src_value else {
return Err(ExpressionError::path_error(
"Path construction must start with a vertex",
));
};
let mut path = Path::new(*src);
let mut remaining = iter;
while let Some(edge_expr) = remaining.next() {
let vertex_expr = remaining.next().ok_or_else(|| {
ExpressionError::path_error(
"Path construction requires alternating edge/vertex elements",
)
})?;
let edge_value = ExpressionEvaluator::evaluate(edge_expr, self)?;
let vertex_value = ExpressionEvaluator::evaluate(vertex_expr, self)?;
let Value::Edge(edge) = edge_value else {
return Err(ExpressionError::path_error(
"Path construction edge element must be an edge",
));
};
let Value::Vertex(vertex) = vertex_value else {
return Err(ExpressionError::path_error(
"Path construction vertex element must be a vertex",
));
};
path.add_step(Step { dst: vertex, edge });
}
Ok(Value::Path(Box::new(path)))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::executor::expression::evaluation_context::DefaultExpressionContext;
use linkrs_core::types::expr::Expression;
use linkrs_core::types::VertexId;
use linkrs_core::vertex_edge_path::{Edge, Tag};
use std::collections::HashMap;
fn context() -> DefaultExpressionContext {
DefaultExpressionContext::new()
}
fn test_vertex() -> Value {
let tag = Tag::new(
"person".to_string(),
HashMap::from([("name".into(), Value::string("Alice"))]),
);
Value::Vertex(Box::new(linkrs_core::vertex_edge_path::Vertex::new(
VertexId::try_from_int64(1).expect("valid vertex id"),
tag,
)))
}
fn test_edge() -> Value {
Value::Edge(Box::new(Edge::new(
VertexId::try_from_int64(1).expect("valid vertex id"),
VertexId::try_from_int64(2).expect("valid vertex id"),
"knows".to_string(),
0,
HashMap::new(),
)))
}
#[test]
fn evaluate_label_default_reports_label() {
let mut ctx = context();
let err = ExpressionEvaluator::evaluate(&Expression::label("Person"), &mut ctx)
.expect_err("label expression cannot be resolved without context");
assert!(
err.message.contains("Person"),
"error should name the label: {}",
err.message
);
}
#[test]
fn evaluate_list_comprehension_with_filter_and_map() {
let mut ctx = context();
let expr = Expression::list_comprehension(
"x",
Expression::list(vec![
Expression::Literal(Value::Int(1)),
Expression::Literal(Value::Int(2)),
Expression::Literal(Value::Int(3)),
Expression::Literal(Value::Int(4)),
]),
Some(Expression::binary(
Expression::variable("x"),
linkrs_core::types::operators::BinaryOperator::GreaterThan,
Expression::Literal(Value::Int(2)),
)),
Some(Expression::binary(
Expression::variable("x"),
linkrs_core::types::operators::BinaryOperator::Multiply,
Expression::Literal(Value::Int(10)),
)),
);
let result = ExpressionEvaluator::evaluate(&expr, &mut ctx).expect("comprehension");
assert_eq!(
result,
Value::list(List::from(vec![Value::Int(30), Value::Int(40)]))
);
}
#[test]
fn evaluate_list_comprehension_empty_and_null() {
let mut ctx = context();
let empty = Expression::list_comprehension(
"x",
Expression::list(Vec::new()),
None,
Some(Expression::variable("x")),
);
assert_eq!(
ExpressionEvaluator::evaluate(&empty, &mut ctx).expect("empty comprehension"),
Value::list(List::from(Vec::<Value>::new()))
);
let null_src = Expression::list_comprehension(
"x",
Expression::Literal(Value::Null(NullType::Null)),
None,
None,
);
assert_eq!(
ExpressionEvaluator::evaluate(&null_src, &mut ctx).expect("null comprehension"),
Value::Null(NullType::Null)
);
let bad =
Expression::list_comprehension("x", Expression::Literal(Value::Int(5)), None, None);
assert!(ExpressionEvaluator::evaluate(&bad, &mut ctx).is_err());
}
#[test]
fn evaluate_predicate_quantifiers() {
let list = Expression::list(vec![
Expression::Literal(Value::Int(1)),
Expression::Literal(Value::Int(2)),
Expression::Literal(Value::Int(3)),
]);
let gt_two = Expression::binary(
Expression::variable("x"),
linkrs_core::types::operators::BinaryOperator::GreaterThan,
Expression::Literal(Value::Int(2)),
);
let args = vec![Expression::variable("x"), list.clone(), gt_two.clone()];
let mut ctx = context();
let all =
ExpressionEvaluator::evaluate(&Expression::predicate("all", args.clone()), &mut ctx)
.expect("all");
assert_eq!(all, Value::Bool(false));
let mut ctx = context();
let any =
ExpressionEvaluator::evaluate(&Expression::predicate("any", args.clone()), &mut ctx)
.expect("any");
assert_eq!(any, Value::Bool(true));
let mut ctx = context();
let single =
ExpressionEvaluator::evaluate(&Expression::predicate("single", args.clone()), &mut ctx)
.expect("single");
assert_eq!(single, Value::Bool(true));
let mut ctx = context();
let none =
ExpressionEvaluator::evaluate(&Expression::predicate("none", args.clone()), &mut ctx)
.expect("none");
assert_eq!(none, Value::Bool(false));
let gt_zero = Expression::binary(
Expression::variable("x"),
linkrs_core::types::operators::BinaryOperator::GreaterThan,
Expression::Literal(Value::Int(0)),
);
let mut ctx = context();
let all_true = ExpressionEvaluator::evaluate(
&Expression::predicate(
"all",
vec![Expression::variable("x"), list.clone(), gt_zero],
),
&mut ctx,
)
.expect("all true");
assert_eq!(all_true, Value::Bool(true));
let mut ctx = context();
assert!(
ExpressionEvaluator::evaluate(&Expression::predicate("maybe", args), &mut ctx,)
.is_err()
);
}
#[test]
fn evaluate_reduce_accumulates() {
let mut ctx = context();
let expr = Expression::reduce(
"acc",
Expression::Literal(Value::Int(0)),
"x",
Expression::list(vec![
Expression::Literal(Value::Int(1)),
Expression::Literal(Value::Int(2)),
Expression::Literal(Value::Int(3)),
]),
Expression::binary(
Expression::variable("acc"),
linkrs_core::types::operators::BinaryOperator::Add,
Expression::variable("x"),
),
);
let result = ExpressionEvaluator::evaluate(&expr, &mut ctx).expect("reduce");
assert_eq!(result, Value::Int(6));
}
#[test]
fn evaluate_label_tag_property_accesses_vertex_property() {
let mut ctx = context().add_variable("n".into(), test_vertex());
let expr = Expression::label_tag_property(Expression::variable("n"), "name");
let result = ExpressionEvaluator::evaluate(&expr, &mut ctx).expect("tag property");
assert_eq!(result, Value::string("Alice"));
}
#[test]
fn evaluate_path_build_assembles_path() {
let mut ctx = context();
let expr = Expression::path_build(vec![
Expression::Literal(test_vertex()),
Expression::Literal(test_edge()),
Expression::Literal(test_vertex()),
]);
let result = ExpressionEvaluator::evaluate(&expr, &mut ctx).expect("path build");
let Value::Path(path) = result else {
panic!("expected a path value");
};
assert_eq!(path.len(), 1);
assert_eq!(
path.src.vid,
VertexId::try_from_int64(1).expect("valid vertex id")
);
let bad = Expression::path_build(vec![Expression::Literal(Value::Int(1))]);
let err = ExpressionEvaluator::evaluate(&bad, &mut ctx).expect_err("path build error");
assert!(err.message.contains("vertex"), "{}", err.message);
}
}