use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
use crate::gql::ast::{
AggregateFunc, BinaryOperator, CallBody, CallClause, CallProcedureClause, CallQuery,
CaseExpression, EdgeDirection, EdgePattern, Expression, GroupByClause, HavingClause, LetClause,
LimitClause, Literal, MatchClause, NodePattern, OptionalMatchClause, OrderClause,
PathQuantifier, Pattern, PatternElement, Query, ReturnClause, ReturnItem, Statement,
UnaryOperator, UnwindClause, WhereClause, WithClause, YieldItem,
};
use crate::gql::error::CompileError;
use crate::storage::cow::Graph;
use crate::traversal::{BoundTraversal, SnapshotLike, Traversal, __};
use crate::value::{IntoValueMap, Value, ValueMap, VertexId};
pub type Parameters = HashMap<String, Value>;
#[derive(Debug, Clone)]
pub struct CompilerConfig {
pub max_pattern_length: usize,
pub max_optional_matches: usize,
pub max_subquery_depth: usize,
pub max_union_clauses: usize,
}
impl Default for CompilerConfig {
fn default() -> Self {
Self {
max_pattern_length: 100,
max_optional_matches: 50,
max_subquery_depth: 10,
max_union_clauses: 50,
}
}
}
impl CompilerConfig {
pub fn unlimited() -> Self {
Self {
max_pattern_length: usize::MAX,
max_optional_matches: usize::MAX,
max_subquery_depth: usize::MAX,
max_union_clauses: usize::MAX,
}
}
pub fn strict() -> Self {
Self {
max_pattern_length: 20,
max_optional_matches: 10,
max_subquery_depth: 3,
max_union_clauses: 10,
}
}
}
#[inline]
fn id_to_value(id: u64) -> Value {
if id > i64::MAX as u64 {
Value::String(id.to_string())
} else {
Value::Int(id as i64)
}
}
fn traverser_to_row(t: &crate::traversal::Traverser) -> std::collections::HashMap<String, Value> {
let mut row = std::collections::HashMap::new();
for label in t.path.all_labels() {
if let Some(values) = t.path.get(label) {
if let Some(pv) = values.last() {
row.insert(label.clone(), pv.to_value());
}
}
}
let path_values: Vec<Value> = t.path.objects().map(|pv| pv.to_value()).collect();
row.insert("__path__".to_string(), Value::List(path_values));
row.insert("__current__".to_string(), t.value.clone());
row
}
pub fn compile<S: SnapshotLike + ?Sized>(
query: &Query,
snapshot: &S,
) -> Result<Vec<Value>, CompileError> {
compile_with_config(
query,
snapshot,
&Parameters::new(),
&CompilerConfig::default(),
)
}
pub fn compile_with_params<S: SnapshotLike + ?Sized>(
query: &Query,
snapshot: &S,
params: &Parameters,
) -> Result<Vec<Value>, CompileError> {
compile_with_config(query, snapshot, params, &CompilerConfig::default())
}
pub fn compile_with_config<S: SnapshotLike + ?Sized>(
query: &Query,
snapshot: &S,
params: &Parameters,
config: &CompilerConfig,
) -> Result<Vec<Value>, CompileError> {
compile_with_config_inner(query, snapshot, params, config, None)
}
fn compile_with_config_inner<S: SnapshotLike + ?Sized>(
query: &Query,
snapshot: &S,
params: &Parameters,
config: &CompilerConfig,
graph_handle: Option<Arc<Graph>>,
) -> Result<Vec<Value>, CompileError> {
let mut compiler = Compiler::new_with_graph(snapshot, params, config, graph_handle);
compiler.compile(query)
}
pub fn compile_statement<S: SnapshotLike + ?Sized>(
stmt: &Statement,
snapshot: &S,
) -> Result<Vec<Value>, CompileError> {
compile_statement_with_config(
stmt,
snapshot,
&Parameters::new(),
&CompilerConfig::default(),
)
}
pub fn compile_statement_with_params<S: SnapshotLike + ?Sized>(
stmt: &Statement,
snapshot: &S,
params: &Parameters,
) -> Result<Vec<Value>, CompileError> {
compile_statement_with_config(stmt, snapshot, params, &CompilerConfig::default())
}
pub fn compile_statement_with_config<S: SnapshotLike + ?Sized>(
stmt: &Statement,
snapshot: &S,
params: &Parameters,
config: &CompilerConfig,
) -> Result<Vec<Value>, CompileError> {
compile_statement_with_config_inner(stmt, snapshot, params, config, None)
}
pub fn compile_statement_with_graph<S: SnapshotLike + ?Sized>(
stmt: &Statement,
snapshot: &S,
graph_handle: Option<Arc<Graph>>,
) -> Result<Vec<Value>, CompileError> {
compile_statement_with_config_inner(
stmt,
snapshot,
&Parameters::new(),
&CompilerConfig::default(),
graph_handle,
)
}
pub fn compile_statement_with_params_and_graph<S: SnapshotLike + ?Sized>(
stmt: &Statement,
snapshot: &S,
params: &Parameters,
graph_handle: Option<Arc<Graph>>,
) -> Result<Vec<Value>, CompileError> {
compile_statement_with_config_inner(
stmt,
snapshot,
params,
&CompilerConfig::default(),
graph_handle,
)
}
fn compile_statement_with_config_inner<S: SnapshotLike + ?Sized>(
stmt: &Statement,
snapshot: &S,
params: &Parameters,
config: &CompilerConfig,
graph_handle: Option<Arc<Graph>>,
) -> Result<Vec<Value>, CompileError> {
match stmt {
Statement::Query(query) => {
compile_with_config_inner(query.as_ref(), snapshot, params, config, graph_handle)
}
Statement::Union { queries, all } => {
if queries.len() > config.max_union_clauses {
return Err(CompileError::complexity_limit_exceeded(format!(
"UNION has {} queries, maximum allowed is {}",
queries.len(),
config.max_union_clauses
)));
}
compile_union_with_config_inner(queries, *all, snapshot, params, config, graph_handle)
}
Statement::Mutation(_) => {
Err(CompileError::UnsupportedFeature(
"Mutation statements require mutable graph access. Use compile_mutation() with GraphMut.".to_string(),
))
}
Statement::Ddl(_) => {
Err(CompileError::UnsupportedFeature(
"DDL statements modify schema, not query data. Use execute_ddl() instead."
.to_string(),
))
}
}
}
#[allow(dead_code)]
fn compile_union<S: SnapshotLike + ?Sized>(
queries: &[Query],
keep_duplicates: bool,
snapshot: &S,
) -> Result<Vec<Value>, CompileError> {
compile_union_with_config(
queries,
keep_duplicates,
snapshot,
&Parameters::new(),
&CompilerConfig::default(),
)
}
#[allow(dead_code)]
fn compile_union_with_params<S: SnapshotLike + ?Sized>(
queries: &[Query],
keep_duplicates: bool,
snapshot: &S,
params: &Parameters,
) -> Result<Vec<Value>, CompileError> {
compile_union_with_config(
queries,
keep_duplicates,
snapshot,
params,
&CompilerConfig::default(),
)
}
fn compile_union_with_config<S: SnapshotLike + ?Sized>(
queries: &[Query],
keep_duplicates: bool,
snapshot: &S,
params: &Parameters,
config: &CompilerConfig,
) -> Result<Vec<Value>, CompileError> {
compile_union_with_config_inner(queries, keep_duplicates, snapshot, params, config, None)
}
fn compile_union_with_config_inner<S: SnapshotLike + ?Sized>(
queries: &[Query],
keep_duplicates: bool,
snapshot: &S,
params: &Parameters,
config: &CompilerConfig,
graph_handle: Option<Arc<Graph>>,
) -> Result<Vec<Value>, CompileError> {
let mut all_results = Vec::new();
for query in queries {
let results =
compile_with_config_inner(query, snapshot, params, config, graph_handle.clone())?;
all_results.extend(results);
}
if keep_duplicates {
Ok(all_results)
} else {
let mut seen: HashSet<ComparableValue> = HashSet::new();
let deduped: Vec<Value> = all_results
.into_iter()
.filter(|v| {
let key = ComparableValue::from(v.clone());
seen.insert(key)
})
.collect();
Ok(deduped)
}
}
struct Compiler<'a, S: SnapshotLike + ?Sized> {
snapshot: &'a S,
bindings: HashMap<String, BindingInfo>,
parameters: &'a Parameters,
has_multi_vars: bool,
config: &'a CompilerConfig,
subquery_depth: usize,
#[allow(dead_code)] graph_handle: Option<Arc<Graph>>,
}
#[derive(Debug, Clone)]
struct BindingInfo {
#[allow(dead_code)]
pattern_index: usize,
#[allow(dead_code)]
is_node: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum ListPredicateKind {
All,
Any,
None,
Single,
}
impl<'a, S: SnapshotLike + ?Sized> Compiler<'a, S> {
fn new_with_graph(
snapshot: &'a S,
parameters: &'a Parameters,
config: &'a CompilerConfig,
graph_handle: Option<Arc<Graph>>,
) -> Self {
Self {
snapshot,
bindings: HashMap::new(),
parameters,
has_multi_vars: false,
config,
subquery_depth: 0,
graph_handle,
}
}
fn validate_query_complexity(&self, query: &Query) -> Result<(), CompileError> {
for pattern in &query.match_clause.patterns {
if pattern.elements.len() > self.config.max_pattern_length {
return Err(CompileError::complexity_limit_exceeded(format!(
"Pattern has {} elements, maximum allowed is {}",
pattern.elements.len(),
self.config.max_pattern_length
)));
}
}
if query.optional_match_clauses.len() > self.config.max_optional_matches {
return Err(CompileError::complexity_limit_exceeded(format!(
"Query has {} OPTIONAL MATCH clauses, maximum allowed is {}",
query.optional_match_clauses.len(),
self.config.max_optional_matches
)));
}
for opt_match in &query.optional_match_clauses {
for pattern in &opt_match.patterns {
if pattern.elements.len() > self.config.max_pattern_length {
return Err(CompileError::complexity_limit_exceeded(format!(
"OPTIONAL MATCH pattern has {} elements, maximum allowed is {}",
pattern.elements.len(),
self.config.max_pattern_length
)));
}
}
}
if self.subquery_depth > self.config.max_subquery_depth {
return Err(CompileError::complexity_limit_exceeded(format!(
"Subquery nesting depth is {}, maximum allowed is {}",
self.subquery_depth, self.config.max_subquery_depth
)));
}
Ok(())
}
#[allow(dead_code)]
fn resolve_parameter(&self, name: &str) -> Result<Value, CompileError> {
self.parameters
.get(name)
.cloned()
.ok_or_else(|| CompileError::unbound_parameter(name))
}
fn count_pattern_variables(pattern: &Pattern) -> usize {
pattern
.elements
.iter()
.filter(|e| match e {
PatternElement::Node(n) => n.variable.is_some(),
PatternElement::Edge(e) => e.variable.is_some(),
})
.count()
}
fn return_uses_path_function(&self, return_clause: &ReturnClause) -> bool {
return_clause
.items
.iter()
.any(|item| Self::expression_uses_path_function(&item.expression))
}
fn expression_uses_path_function(expr: &Expression) -> bool {
match expr {
Expression::FunctionCall { name, .. } if name.eq_ignore_ascii_case("path") => true,
Expression::BinaryOp { left, right, .. } => {
Self::expression_uses_path_function(left)
|| Self::expression_uses_path_function(right)
}
Expression::UnaryOp { expr, .. } => Self::expression_uses_path_function(expr),
Expression::List(items) => items.iter().any(Self::expression_uses_path_function),
Expression::Map(entries) => entries
.iter()
.any(|(_, value)| Self::expression_uses_path_function(value)),
Expression::FunctionCall { args, .. } => {
args.iter().any(Self::expression_uses_path_function)
}
Expression::Aggregate { expr, .. } => Self::expression_uses_path_function(expr),
Expression::Case(case_expr) => {
case_expr.when_clauses.iter().any(|(c, r)| {
Self::expression_uses_path_function(c) || Self::expression_uses_path_function(r)
}) || case_expr
.else_clause
.as_ref()
.map(|e| Self::expression_uses_path_function(e))
.unwrap_or(false)
}
_ => false,
}
}
fn has_edge_variable(pattern: &Pattern) -> bool {
pattern
.elements
.iter()
.any(|e| matches!(e, PatternElement::Edge(edge) if edge.variable.is_some()))
}
fn compile(&mut self, query: &Query) -> Result<Vec<Value>, CompileError> {
self.validate_query_complexity(query)?;
if query.match_clause.patterns.is_empty() {
return Err(CompileError::EmptyPattern);
}
if query.match_clause.patterns.len() > 1
&& query.optional_match_clauses.is_empty()
&& query.with_clauses.is_empty()
&& query.unwind_clauses.is_empty()
&& query.call_clauses.is_empty()
&& query.let_clauses.is_empty()
&& query.group_by_clause.is_none()
{
return self.execute_multi_pattern_query(query);
}
let pattern = &query.match_clause.patterns[0];
if pattern.elements.is_empty() {
return Err(CompileError::EmptyPattern);
}
let var_count = Self::count_pattern_variables(pattern);
let has_edge_var = Self::has_edge_variable(pattern);
let has_optional = !query.optional_match_clauses.is_empty();
let has_with_path = query.with_path_clause.is_some();
let uses_path_func = self.return_uses_path_function(&query.return_clause);
let has_unwind = !query.unwind_clauses.is_empty();
let has_let = !query.let_clauses.is_empty();
let has_with = !query.with_clauses.is_empty();
let has_call = !query.call_clauses.is_empty();
let has_procedure_calls = !query.call_procedure_clauses.is_empty();
self.has_multi_vars = var_count > 1
|| has_edge_var
|| has_optional
|| has_with_path
|| uses_path_func
|| has_unwind
|| has_let
|| has_with
|| has_call
|| has_procedure_calls;
let g = crate::traversal::GraphTraversalSource::from_snapshot(self.snapshot);
let traversal = g.v();
let traversal = if self.has_multi_vars {
traversal.with_path()
} else {
traversal
};
let traversal = self.compile_pattern(pattern, traversal)?;
for opt_clause in &query.optional_match_clauses {
for opt_pattern in &opt_clause.patterns {
self.register_optional_pattern_variables(opt_pattern);
}
}
for unwind in &query.unwind_clauses {
self.bindings.insert(
unwind.alias.clone(),
BindingInfo {
pattern_index: 0,
is_node: false,
},
);
}
for let_clause in &query.let_clauses {
self.bindings.insert(
let_clause.variable.clone(),
BindingInfo {
pattern_index: 0,
is_node: false,
},
);
}
for with_clause in &query.with_clauses {
for item in &with_clause.items {
let var_name = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
self.bindings.insert(
var_name,
BindingInfo {
pattern_index: 0,
is_node: false,
},
);
}
}
for call_clause in &query.call_clauses {
self.validate_call_clause(call_clause)?;
self.register_call_clause_variables(call_clause);
}
for proc_clause in &query.call_procedure_clauses {
for item in &proc_clause.yield_items {
let var_name = item.alias.as_ref().unwrap_or(&item.field).clone();
self.bindings.insert(
var_name,
BindingInfo {
pattern_index: 0,
is_node: false,
},
);
}
}
for item in &query.return_clause.items {
self.validate_expression_variables(&item.expression)?;
}
if let Some(where_cl) = &query.where_clause {
self.validate_expression_variables(&where_cl.expression)?;
}
if let Some(group_by) = &query.group_by_clause {
for expr in &group_by.expressions {
self.validate_expression_variables(expr)?;
}
}
if let Some(group_by) = &query.group_by_clause {
let results = self.execute_group_by_query(
&query.return_clause,
&query.where_clause,
group_by,
&query.having_clause,
traversal,
)?;
let results =
self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
return Ok(results);
}
if has_optional {
let results = self.execute_with_optional_match(
&query.return_clause,
&query.where_clause,
&query.let_clauses,
&query.optional_match_clauses,
traversal,
)?;
let results =
self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
return Ok(results);
}
if !query.unwind_clauses.is_empty() {
return self.execute_with_unwind(query, traversal);
}
if has_call || has_procedure_calls {
return self.execute_with_call_clauses(query, traversal);
}
if !query.with_clauses.is_empty() {
return self.execute_with_with_clauses(query, traversal);
}
if !query.let_clauses.is_empty() {
return self.execute_with_let(query, traversal);
}
let results = self.execute_return(
&query.return_clause,
&query.where_clause,
&query.having_clause,
traversal,
)?;
let results = self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
Ok(results)
}
fn execute_multi_pattern_query(&mut self, query: &Query) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
self.has_multi_vars = true;
let first_pattern = &query.match_clause.patterns[0];
if first_pattern.elements.is_empty() {
return Err(CompileError::EmptyPattern);
}
let g = crate::traversal::GraphTraversalSource::from_snapshot(self.snapshot);
let traversal = g.v().with_path();
let traversal = self.compile_pattern(first_pattern, traversal)?;
for pattern in query.match_clause.patterns.iter().skip(1) {
for element in &pattern.elements {
match element {
PatternElement::Node(node) => {
if let Some(var) = &node.variable {
self.bindings.entry(var.clone()).or_insert(BindingInfo {
pattern_index: 0,
is_node: true,
});
}
}
PatternElement::Edge(edge) => {
if let Some(var) = &edge.variable {
self.bindings.entry(var.clone()).or_insert(BindingInfo {
pattern_index: 0,
is_node: false,
});
}
}
}
}
}
for proc_clause in &query.call_procedure_clauses {
for item in &proc_clause.yield_items {
let var_name = item.alias.as_ref().unwrap_or(&item.field).clone();
self.bindings.entry(var_name).or_insert(BindingInfo {
pattern_index: 0,
is_node: false,
});
}
}
for item in &query.return_clause.items {
self.validate_expression_variables(&item.expression)?;
}
if let Some(where_cl) = &query.where_clause {
self.validate_expression_variables(&where_cl.expression)?;
}
let mut rows: Vec<HashMap<String, Value>> = traversal
.execute()
.map(|t: Traverser| traverser_to_row(&t))
.collect();
for pattern in query.match_clause.patterns.iter().skip(1) {
rows = self.expand_rows_with_pattern(rows, pattern)?;
if rows.is_empty() {
break;
}
}
if let Some(where_cl) = &query.where_clause {
rows.retain(|row| self.evaluate_predicate_from_row(&where_cl.expression, row));
}
for proc_clause in &query.call_procedure_clauses {
rows = self.execute_call_procedure(rows, proc_clause)?;
}
let mut results: Vec<Value> = rows
.into_iter()
.filter_map(|row| self.evaluate_return_for_row(&query.return_clause.items, &row))
.collect();
if query.return_clause.distinct {
results = self.deduplicate_results(results);
}
let results = self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
Ok(results)
}
fn expand_rows_with_pattern(
&self,
rows: Vec<HashMap<String, Value>>,
pattern: &Pattern,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
if pattern.elements.is_empty() {
return Ok(rows);
}
let anchor_var = match &pattern.elements[0] {
PatternElement::Node(node) => node.variable.clone(),
PatternElement::Edge(_) => None,
};
let anchored = anchor_var
.as_ref()
.and_then(|v| rows.first().map(|row| row.contains_key(v)))
.unwrap_or(false);
let g = crate::traversal::GraphTraversalSource::from_snapshot(self.snapshot);
let mut out: Vec<HashMap<String, Value>> = Vec::new();
for row in rows {
let mut traversal = if anchored {
let var = anchor_var.as_ref().expect("anchored => anchor_var is Some");
let vid = match row.get(var) {
Some(Value::Vertex(v)) => *v,
_ => continue,
};
g.v_ids([vid]).with_path()
} else {
g.v().with_path()
};
let mut is_first = true;
for element in &pattern.elements {
match element {
PatternElement::Node(node) => {
let skip_filters = is_first && anchored;
is_first = false;
if !skip_filters {
traversal = self.apply_node_filters(node, traversal);
}
if let Some(var) = &node.variable {
if let Some(existing) = row.get(var) {
if !(skip_filters) {
if let Value::Vertex(target) = existing {
let target_id = *target;
traversal = traversal.filter(move |_ctx, v| {
matches!(v, Value::Vertex(id) if *id == target_id)
});
} else {
traversal = traversal.filter(|_ctx, _v| false);
}
}
traversal = traversal.as_(var.as_str());
} else {
traversal = traversal.as_(var.as_str());
}
}
}
PatternElement::Edge(edge) => {
is_first = false;
traversal = self.apply_edge_navigation(edge, traversal);
if let Some(var) = &edge.variable {
traversal = traversal.as_(var.as_str());
}
}
}
}
for traverser in traversal.execute() {
let mut new_row = row.clone();
for label in traverser.path.all_labels() {
if let Some(values) = traverser.path.get(label) {
if let Some(pv) = values.last() {
new_row.insert(label.clone(), pv.to_value());
}
}
}
out.push(new_row);
}
}
Ok(out)
}
fn execute_with_unwind(
&mut self,
query: &Query,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
let base_traversers: Vec<Traverser> = if self.has_multi_vars {
traversal.execute().collect()
} else {
traversal
.to_list()
.into_iter()
.map(Traverser::new)
.collect()
};
let mut current_rows: Vec<HashMap<String, Value>> = base_traversers
.into_iter()
.map(|t| {
let mut row = HashMap::new();
for label in t.path.all_labels() {
if let Some(values) = t.path.get(label) {
if let Some(path_value) = values.last() {
row.insert(label.clone(), path_value.to_value());
}
}
}
let path_values: Vec<Value> = t.path.objects().map(|pv| pv.to_value()).collect();
row.insert("__path__".to_string(), Value::List(path_values));
row.insert("__current__".to_string(), t.value);
row
})
.collect();
for unwind in &query.unwind_clauses {
current_rows = self.apply_unwind(current_rows, unwind)?;
}
let filtered_rows: Vec<HashMap<String, Value>> = if let Some(where_cl) = &query.where_clause
{
current_rows
.into_iter()
.filter(|row| self.evaluate_predicate_from_row(&where_cl.expression, row))
.collect()
} else {
current_rows
};
let filtered_rows = self.apply_let_clauses(filtered_rows, &query.let_clauses);
let results: Vec<Value> = filtered_rows
.into_iter()
.filter_map(|row| self.evaluate_return_for_row(&query.return_clause.items, &row))
.collect();
let results = if query.return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
let results = self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
Ok(results)
}
fn execute_with_let(
&self,
query: &Query,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
let base_traversers: Vec<Traverser> = if self.has_multi_vars {
traversal.execute().collect()
} else {
traversal
.to_list()
.into_iter()
.map(Traverser::new)
.collect()
};
let current_rows: Vec<HashMap<String, Value>> = base_traversers
.into_iter()
.map(|t| {
let mut row = HashMap::new();
for label in t.path.all_labels() {
if let Some(values) = t.path.get(label) {
if let Some(path_value) = values.last() {
row.insert(label.clone(), path_value.to_value());
}
}
}
let path_values: Vec<Value> = t.path.objects().map(|pv| pv.to_value()).collect();
row.insert("__path__".to_string(), Value::List(path_values));
row.insert("__current__".to_string(), t.value);
row
})
.collect();
let filtered_rows: Vec<HashMap<String, Value>> = if let Some(where_cl) = &query.where_clause
{
current_rows
.into_iter()
.filter(|row| self.evaluate_predicate_from_row(&where_cl.expression, row))
.collect()
} else {
current_rows
};
let filtered_rows = self.apply_let_clauses(filtered_rows, &query.let_clauses);
let results: Vec<Value> = filtered_rows
.into_iter()
.filter_map(|row| self.evaluate_return_for_row(&query.return_clause.items, &row))
.collect();
let results = if query.return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
let results = self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
Ok(results)
}
fn execute_with_with_clauses(
&self,
query: &Query,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
let base_traversers: Vec<Traverser> = if self.has_multi_vars {
traversal.execute().collect()
} else {
traversal
.to_list()
.into_iter()
.map(Traverser::new)
.collect()
};
let mut current_rows: Vec<HashMap<String, Value>> = base_traversers
.into_iter()
.map(|t| {
let mut row = HashMap::new();
for label in t.path.all_labels() {
if let Some(values) = t.path.get(label) {
if let Some(path_value) = values.last() {
row.insert(label.clone(), path_value.to_value());
}
}
}
let path_values: Vec<Value> = t.path.objects().map(|pv| pv.to_value()).collect();
row.insert("__path__".to_string(), Value::List(path_values));
row.insert("__current__".to_string(), t.value);
row
})
.collect();
if let Some(where_cl) = &query.where_clause {
current_rows.retain(|row| self.evaluate_predicate_from_row(&where_cl.expression, row));
}
for with_clause in &query.with_clauses {
current_rows = self.apply_with_clause(current_rows, with_clause)?;
}
let current_rows = self.apply_let_clauses(current_rows, &query.let_clauses);
let results: Vec<Value> = current_rows
.into_iter()
.filter_map(|row| self.evaluate_return_for_row(&query.return_clause.items, &row))
.collect();
let results = if query.return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
let results = self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
Ok(results)
}
fn apply_with_clause(
&self,
rows: Vec<HashMap<String, Value>>,
with_clause: &WithClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
if rows.is_empty() {
return Ok(rows);
}
let has_aggregates = with_clause
.items
.iter()
.any(|item| Self::expr_has_aggregate(&item.expression));
let mut new_rows = if has_aggregates {
self.apply_with_aggregation(&rows, with_clause)?
} else {
self.apply_with_projection(&rows, with_clause)?
};
if with_clause.distinct {
new_rows = self.deduplicate_rows(new_rows);
}
if let Some(where_cl) = &with_clause.where_clause {
new_rows.retain(|row| self.evaluate_predicate_from_row(&where_cl.expression, row));
}
if let Some(order_clause) = &with_clause.order_clause {
new_rows = self.apply_order_by_to_rows(new_rows, order_clause)?;
}
if let Some(limit_clause) = &with_clause.limit_clause {
new_rows = self.apply_limit_to_rows(new_rows, limit_clause);
}
Ok(new_rows)
}
fn apply_with_projection(
&self,
rows: &[HashMap<String, Value>],
with_clause: &WithClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let mut new_rows = Vec::with_capacity(rows.len());
for row in rows {
let mut new_row = HashMap::new();
for item in &with_clause.items {
let key = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let value = self.evaluate_expression_from_row(&item.expression, row);
new_row.insert(key, value);
}
new_rows.push(new_row);
}
Ok(new_rows)
}
fn apply_with_aggregation(
&self,
rows: &[HashMap<String, Value>],
with_clause: &WithClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let mut group_items: Vec<&ReturnItem> = Vec::new();
let mut aggregate_items: Vec<&ReturnItem> = Vec::new();
for item in &with_clause.items {
if Self::expr_has_aggregate(&item.expression) {
aggregate_items.push(item);
} else {
group_items.push(item);
}
}
if group_items.is_empty() {
let mut result_row = HashMap::new();
for item in &aggregate_items {
let key = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let value = self.compute_aggregate_over_rows(rows, &item.expression);
result_row.insert(key, value);
}
return Ok(vec![result_row]);
}
let mut groups: HashMap<Vec<ComparableValue>, Vec<&HashMap<String, Value>>> =
HashMap::new();
for row in rows {
let group_key: Vec<ComparableValue> = group_items
.iter()
.map(|item| {
let val = self.evaluate_expression_from_row(&item.expression, row);
ComparableValue::from(val)
})
.collect();
groups.entry(group_key).or_default().push(row);
}
let mut new_rows = Vec::with_capacity(groups.len());
for (group_key, group_rows) in groups {
let mut new_row = HashMap::new();
for (i, item) in group_items.iter().enumerate() {
let key = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let value = Value::from(group_key[i].clone());
new_row.insert(key, value);
}
let owned_rows: Vec<HashMap<String, Value>> = group_rows.into_iter().cloned().collect();
for item in &aggregate_items {
let key = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let value = self.compute_aggregate_over_rows(&owned_rows, &item.expression);
new_row.insert(key, value);
}
new_rows.push(new_row);
}
Ok(new_rows)
}
fn compute_aggregate_over_rows(
&self,
rows: &[HashMap<String, Value>],
expr: &Expression,
) -> Value {
match expr {
Expression::Aggregate {
func,
distinct,
expr: inner_expr,
} => {
let mut values: Vec<Value> = rows
.iter()
.map(|row| self.evaluate_expression_from_row(inner_expr, row))
.filter(|v| !matches!(v, Value::Null))
.collect();
if *distinct {
let mut seen = HashSet::new();
values.retain(|v| {
let key = format!("{:?}", v);
seen.insert(key)
});
}
match func {
AggregateFunc::Count => Value::Int(values.len() as i64),
AggregateFunc::Sum => self.compute_sum(&values),
AggregateFunc::Avg => self.compute_avg(&values),
AggregateFunc::Min => self.compute_min(&values),
AggregateFunc::Max => self.compute_max(&values),
AggregateFunc::Collect => Value::List(values),
}
}
Expression::BinaryOp { left, right, op } => {
let left_val = self.compute_aggregate_over_rows(rows, left);
let right_val = self.compute_aggregate_over_rows(rows, right);
apply_binary_op(*op, left_val, right_val)
}
_ => {
if let Some(row) = rows.first() {
self.evaluate_expression_from_row(expr, row)
} else {
Value::Null
}
}
}
}
fn compute_sum(&self, values: &[Value]) -> Value {
let mut float_sum = 0.0;
let mut is_int = true;
let mut int_sum: i64 = 0;
let mut overflow = false;
for v in values {
match v {
Value::Int(i) => {
if is_int && !overflow {
match int_sum.checked_add(*i) {
Some(s) => int_sum = s,
None => overflow = true, }
}
float_sum += *i as f64;
}
Value::Float(f) => {
is_int = false;
float_sum += f;
}
_ => {}
}
}
if is_int && !overflow {
Value::Int(int_sum)
} else {
Value::Float(float_sum)
}
}
fn compute_avg(&self, values: &[Value]) -> Value {
if values.is_empty() {
return Value::Null;
}
let mut sum = 0.0;
let mut count = 0;
for v in values {
match v {
Value::Int(i) => {
sum += *i as f64;
count += 1;
}
Value::Float(f) => {
sum += f;
count += 1;
}
_ => {}
}
}
if count > 0 {
Value::Float(sum / count as f64)
} else {
Value::Null
}
}
fn compute_min(&self, values: &[Value]) -> Value {
values
.iter()
.filter(|v| !matches!(v, Value::Null))
.min_by(|a, b| compare_values(a, b))
.cloned()
.unwrap_or(Value::Null)
}
fn compute_max(&self, values: &[Value]) -> Value {
values
.iter()
.filter(|v| !matches!(v, Value::Null))
.max_by(|a, b| compare_values(a, b))
.cloned()
.unwrap_or(Value::Null)
}
fn deduplicate_rows(&self, rows: Vec<HashMap<String, Value>>) -> Vec<HashMap<String, Value>> {
let mut seen = HashSet::new();
let mut result = Vec::new();
for row in rows {
let key = self.row_to_comparable_key(&row);
if seen.insert(key) {
result.push(row);
}
}
result
}
fn row_to_comparable_key(&self, row: &HashMap<String, Value>) -> String {
let mut pairs: Vec<_> = row.iter().collect();
pairs.sort_by(|a, b| a.0.cmp(b.0));
format!("{:?}", pairs)
}
fn apply_order_by_to_rows(
&self,
mut rows: Vec<HashMap<String, Value>>,
order_clause: &OrderClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
rows.sort_by(|a, b| {
for order_item in &order_clause.items {
let val_a = self.evaluate_expression_from_row(&order_item.expression, a);
let val_b = self.evaluate_expression_from_row(&order_item.expression, b);
let cmp = compare_values(&val_a, &val_b);
if cmp != std::cmp::Ordering::Equal {
return if order_item.descending {
cmp.reverse()
} else {
cmp
};
}
}
std::cmp::Ordering::Equal
});
Ok(rows)
}
fn apply_limit_to_rows(
&self,
rows: Vec<HashMap<String, Value>>,
limit_clause: &LimitClause,
) -> Vec<HashMap<String, Value>> {
let offset = limit_clause.offset.unwrap_or(0) as usize;
let limit = limit_clause.limit as usize;
rows.into_iter().skip(offset).take(limit).collect()
}
fn expression_to_key(expr: &Expression) -> String {
match expr {
Expression::Variable(name) => name.clone(),
Expression::Property { variable, property } => format!("{}.{}", variable, property),
Expression::Aggregate { func, expr, .. } => {
let inner = Self::expression_to_key(expr);
match func {
AggregateFunc::Count => format!("count({})", inner),
AggregateFunc::Sum => format!("sum({})", inner),
AggregateFunc::Avg => format!("avg({})", inner),
AggregateFunc::Min => format!("min({})", inner),
AggregateFunc::Max => format!("max({})", inner),
AggregateFunc::Collect => format!("collect({})", inner),
}
}
Expression::FunctionCall { name, args } => {
let args_str: Vec<String> = args.iter().map(Self::expression_to_key).collect();
format!("{}({})", name, args_str.join(", "))
}
Expression::Literal(lit) => match lit {
Literal::Int(i) => i.to_string(),
Literal::Float(f) => f.to_string(),
Literal::String(s) => format!("\"{}\"", s),
Literal::Bool(b) => b.to_string(),
Literal::Null => "null".to_string(),
},
_ => format!("{:?}", expr),
}
}
fn apply_unwind(
&self,
rows: Vec<HashMap<String, Value>>,
unwind: &UnwindClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let mut result = Vec::new();
for row in rows {
let list_value = self.evaluate_expression_from_row(&unwind.expression, &row);
match list_value {
Value::List(items) => {
for item in items {
let mut new_row = row.clone();
new_row.insert(unwind.alias.clone(), item);
result.push(new_row);
}
}
Value::Null => {
}
other => {
let mut new_row = row.clone();
new_row.insert(unwind.alias.clone(), other);
result.push(new_row);
}
}
}
Ok(result)
}
fn apply_let_clauses(
&self,
rows: Vec<HashMap<String, Value>>,
let_clauses: &[LetClause],
) -> Vec<HashMap<String, Value>> {
if let_clauses.is_empty() {
return rows;
}
let mut current_rows = rows;
for let_clause in let_clauses {
current_rows = self.apply_single_let_clause(current_rows, let_clause);
}
current_rows
}
fn apply_single_let_clause(
&self,
rows: Vec<HashMap<String, Value>>,
let_clause: &LetClause,
) -> Vec<HashMap<String, Value>> {
if rows.is_empty() {
return rows;
}
if Self::expr_has_aggregate(&let_clause.expression) {
let aggregate_value = self.compute_let_aggregate(&rows, &let_clause.expression);
rows.into_iter()
.map(|mut row| {
row.insert(let_clause.variable.clone(), aggregate_value.clone());
row
})
.collect()
} else {
rows.into_iter()
.map(|mut row| {
let value = self.evaluate_expression_from_row(&let_clause.expression, &row);
row.insert(let_clause.variable.clone(), value);
row
})
.collect()
}
}
fn compute_let_aggregate(&self, rows: &[HashMap<String, Value>], expr: &Expression) -> Value {
match expr {
Expression::Aggregate {
func,
distinct,
expr: inner_expr,
} => {
let mut values: Vec<Value> = rows
.iter()
.map(|row| self.evaluate_expression_from_row(inner_expr, row))
.filter(|v| !matches!(v, Value::Null))
.collect();
if *distinct {
let mut seen = HashSet::new();
values.retain(|v| {
let key = format!("{:?}", v);
seen.insert(key)
});
}
match func {
AggregateFunc::Count => Value::Int(values.len() as i64),
AggregateFunc::Sum => {
let mut float_sum = 0.0;
let mut is_int = true;
let mut int_sum: i64 = 0;
let mut overflow = false;
for val in &values {
match val {
Value::Int(n) => {
if is_int && !overflow {
match int_sum.checked_add(*n) {
Some(s) => int_sum = s,
None => overflow = true, }
}
float_sum += *n as f64;
}
Value::Float(f) => {
is_int = false;
float_sum += f;
}
_ => {}
}
}
if is_int && !overflow {
Value::Int(int_sum)
} else {
Value::Float(float_sum)
}
}
AggregateFunc::Avg => {
if values.is_empty() {
return Value::Null;
}
let mut sum = 0.0;
let mut count = 0;
for val in &values {
match val {
Value::Int(n) => {
sum += *n as f64;
count += 1;
}
Value::Float(f) => {
sum += f;
count += 1;
}
_ => {}
}
}
if count > 0 {
Value::Float(sum / count as f64)
} else {
Value::Null
}
}
AggregateFunc::Min => values
.into_iter()
.min_by(compare_values)
.unwrap_or(Value::Null),
AggregateFunc::Max => values
.into_iter()
.max_by(compare_values)
.unwrap_or(Value::Null),
AggregateFunc::Collect => Value::List(values),
}
}
Expression::BinaryOp { left, op, right } => {
let left_val = self.compute_let_aggregate(rows, left);
let right_val = self.compute_let_aggregate(rows, right);
apply_binary_op(*op, left_val, right_val)
}
Expression::UnaryOp { op, expr } => {
let val = self.compute_let_aggregate(rows, expr);
match op {
UnaryOperator::Not => match val {
Value::Bool(b) => Value::Bool(!b),
_ => Value::Null,
},
UnaryOperator::Neg => match val {
Value::Int(n) => Value::Int(-n),
Value::Float(f) => Value::Float(-f),
_ => Value::Null,
},
}
}
Expression::FunctionCall { name, args } => {
if name.to_uppercase() == "SIZE" {
if let Some(arg) = args.first() {
let val = self.compute_let_aggregate(rows, arg);
return match val {
Value::List(l) => Value::Int(l.len() as i64),
Value::String(s) => Value::Int(s.len() as i64),
_ => Value::Null,
};
}
}
if !rows.is_empty() {
self.evaluate_function_call_from_row(name, args, &rows[0])
} else {
Value::Null
}
}
_ => {
if !rows.is_empty() {
self.evaluate_expression_from_row(expr, &rows[0])
} else {
Value::Null
}
}
}
}
fn validate_call_clause(&self, call_clause: &CallClause) -> Result<(), CompileError> {
match &call_clause.body {
CallBody::Single(query) => self.validate_call_query(query)?,
CallBody::Union { queries, .. } => {
for query in queries {
self.validate_call_query(query)?;
}
}
}
Ok(())
}
fn validate_call_query(&self, query: &CallQuery) -> Result<(), CompileError> {
if let Some(importing_with) = &query.importing_with {
for item in &importing_with.items {
let var_name = match &item.expression {
Expression::Variable(name) => name.clone(),
Expression::Property { variable, .. } => variable.clone(),
_ => continue, };
if !self.bindings.contains_key(&var_name) {
return Err(CompileError::undefined_variable(&var_name));
}
}
}
for item in &query.return_clause.items {
let returned_var = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let is_imported = query
.importing_with
.as_ref()
.map(|iw| {
iw.items.iter().any(|imp_item| {
let imp_var = imp_item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&imp_item.expression));
imp_var == returned_var
})
})
.unwrap_or(false);
if self.bindings.contains_key(&returned_var) && !is_imported {
return Err(CompileError::duplicate_variable(&returned_var));
}
}
for nested_call in &query.call_clauses {
self.validate_call_clause(nested_call)?;
}
Ok(())
}
fn register_call_clause_variables(&mut self, call_clause: &CallClause) {
let return_items = match &call_clause.body {
CallBody::Single(query) => &query.return_clause.items,
CallBody::Union { queries, .. } => {
if let Some(first) = queries.first() {
&first.return_clause.items
} else {
return;
}
}
};
for item in return_items {
let var_name = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
self.bindings.insert(
var_name,
BindingInfo {
pattern_index: 0,
is_node: false,
},
);
}
}
fn execute_with_call_clauses(
&self,
query: &Query,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
let base_traversers: Vec<Traverser> = if self.has_multi_vars {
traversal.execute().collect()
} else {
traversal
.to_list()
.into_iter()
.map(Traverser::new)
.collect()
};
let mut current_rows: Vec<HashMap<String, Value>> = base_traversers
.into_iter()
.map(|t| {
let mut row = HashMap::new();
for label in t.path.all_labels() {
if let Some(values) = t.path.get(label) {
if let Some(path_value) = values.last() {
row.insert(label.clone(), path_value.to_value());
}
}
}
let path_values: Vec<Value> = t.path.objects().map(|pv| pv.to_value()).collect();
row.insert("__path__".to_string(), Value::List(path_values));
row.insert("__current__".to_string(), t.value);
row
})
.collect();
if let Some(where_cl) = &query.where_clause {
current_rows.retain(|row| self.evaluate_predicate_from_row(&where_cl.expression, row));
}
for call_clause in &query.call_clauses {
current_rows = self.execute_call_clause(current_rows, call_clause)?;
}
for proc_clause in &query.call_procedure_clauses {
current_rows = self.execute_call_procedure(current_rows, proc_clause)?;
}
let current_rows = self.apply_let_clauses(current_rows, &query.let_clauses);
let mut current_rows = current_rows;
for with_clause in &query.with_clauses {
current_rows = self.apply_with_clause(current_rows, with_clause)?;
}
let results: Vec<Value> = current_rows
.into_iter()
.filter_map(|row| self.evaluate_return_for_row(&query.return_clause.items, &row))
.collect();
let results = if query.return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
let results = self.apply_order_by(&query.order_clause, &query.return_clause, results)?;
let results = self.apply_limit(&query.limit_clause, results);
Ok(results)
}
fn execute_call_clause(
&self,
rows: Vec<HashMap<String, Value>>,
call_clause: &CallClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
if rows.is_empty() {
return Ok(rows);
}
if call_clause.is_correlated() {
self.execute_correlated_call(rows, call_clause)
} else {
self.execute_uncorrelated_call(rows, call_clause)
}
}
fn execute_call_procedure(
&self,
rows: Vec<HashMap<String, Value>>,
proc_clause: &CallProcedureClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
if rows.is_empty() {
return Ok(rows);
}
let mut result_rows = Vec::new();
for outer_row in rows {
let args: Vec<Value> = proc_clause
.arguments
.iter()
.map(|expr| self.evaluate_expression_from_row(expr, &outer_row))
.collect();
let proc_results = self.dispatch_procedure(
&proc_clause.procedure_name,
&args,
&proc_clause.yield_items,
)?;
for proc_row in proc_results {
let mut combined = outer_row.clone();
combined.extend(proc_row);
result_rows.push(combined);
}
}
Ok(result_rows)
}
fn dispatch_procedure(
&self,
name: &str,
args: &[Value],
yield_items: &[YieldItem],
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
use crate::traversal::algorithm_steps::{
bfs_shortest_path, dijkstra_on_storage, expand_from_storage, StepDirection,
};
let storage = self.snapshot.storage();
match name {
"interstellar.shortestPath" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let target = self.extract_vertex_id_arg(name, args, 1, "target")?;
match bfs_shortest_path(storage, source, target, StepDirection::Out) {
Some(path_value) => {
let distance = match &path_value {
Value::List(v) => Value::Int(v.len() as i64 - 1),
_ => Value::Int(0),
};
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "path", path_value);
self.bind_yield(&mut row, yield_items, "distance", distance);
Ok(vec![row])
}
None => Ok(vec![]), }
}
"interstellar.dijkstra" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let target = self.extract_vertex_id_arg(name, args, 1, "target")?;
let weight_prop = self.extract_string_arg(name, args, 2, "weightProperty")?;
match dijkstra_on_storage(storage, source, target, &weight_prop, StepDirection::Out)
{
Some(Value::Map(map)) => {
let path = map.get("path").cloned().unwrap_or(Value::List(Vec::new()));
let weight = map.get("weight").cloned().unwrap_or(Value::Float(0.0));
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "path", path);
self.bind_yield(&mut row, yield_items, "distance", weight);
Ok(vec![row])
}
_ => Ok(vec![]), }
}
"interstellar.kShortestPaths" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let target = self.extract_vertex_id_arg(name, args, 1, "target")?;
let _k = self.extract_int_arg(name, args, 2, "k")? as usize;
let weight_prop = self.extract_string_arg(name, args, 3, "weightProperty")?;
let mut results = Vec::new();
if let Some(Value::Map(map)) =
dijkstra_on_storage(storage, source, target, &weight_prop, StepDirection::Out)
{
let path = map.get("path").cloned().unwrap_or(Value::List(Vec::new()));
let weight = map.get("weight").cloned().unwrap_or(Value::Float(0.0));
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "path", path);
self.bind_yield(&mut row, yield_items, "distance", weight);
self.bind_yield(&mut row, yield_items, "index", Value::Int(0));
results.push(row);
}
Ok(results)
}
"interstellar.bfs" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let mut results = Vec::new();
let mut visited = std::collections::HashSet::new();
let mut queue = std::collections::VecDeque::new();
visited.insert(source);
queue.push_back((source, 0i64));
while let Some((vid, depth)) = queue.pop_front() {
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "node", Value::Vertex(vid));
self.bind_yield(&mut row, yield_items, "depth", Value::Int(depth));
results.push(row);
let neighbors = expand_from_storage(storage, vid, StepDirection::Out);
for (neighbor, _, _) in neighbors {
if visited.insert(neighbor) {
queue.push_back((neighbor, depth + 1));
}
}
}
Ok(results)
}
"interstellar.dfs" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let max_depth = if args.len() > 1 {
Some(self.extract_int_arg(name, args, 1, "maxDepth")? as u32)
} else {
None
};
use crate::traversal::algorithm_steps::{expand_from_storage, StepDirection};
let mut results = Vec::new();
let mut visited = std::collections::HashSet::new();
let mut stack = vec![(source, 0i64)];
while let Some((vid, depth)) = stack.pop() {
if !visited.insert(vid) {
continue;
}
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "node", Value::Vertex(vid));
self.bind_yield(&mut row, yield_items, "depth", Value::Int(depth));
results.push(row);
if let Some(max) = max_depth {
if depth >= max as i64 {
continue;
}
}
let neighbors = expand_from_storage(storage, vid, StepDirection::Out);
for (neighbor, _, _) in neighbors.into_iter().rev() {
if !visited.contains(&neighbor) {
stack.push((neighbor, depth + 1));
}
}
}
Ok(results)
}
"interstellar.astar" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let target = self.extract_vertex_id_arg(name, args, 1, "target")?;
let weight_prop = self.extract_string_arg(name, args, 2, "weightProperty")?;
let heuristic_prop =
self.extract_string_arg(name, args, 3, "heuristicProperty")?;
use crate::traversal::algorithm_steps::astar_on_storage;
use crate::traversal::algorithm_steps::StepDirection;
match astar_on_storage(
storage, source, target, &weight_prop, &heuristic_prop, StepDirection::Out,
) {
Some(Value::Map(map)) => {
let path = map.get("path").cloned().unwrap_or(Value::List(Vec::new()));
let weight = map.get("weight").cloned().unwrap_or(Value::Float(0.0));
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "path", path);
self.bind_yield(&mut row, yield_items, "distance", weight);
Ok(vec![row])
}
_ => Ok(vec![]),
}
}
"interstellar.bidirectionalBfs" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let target = self.extract_vertex_id_arg(name, args, 1, "target")?;
use crate::traversal::algorithm_steps::bidirectional_bfs_on_storage;
match bidirectional_bfs_on_storage(storage, source, target) {
Some(path_value) => {
let distance = match &path_value {
Value::List(v) => Value::Int(v.len() as i64 - 1),
_ => Value::Int(0),
};
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "path", path_value);
self.bind_yield(&mut row, yield_items, "distance", distance);
Ok(vec![row])
}
None => Ok(vec![]),
}
}
"interstellar.iddfs" => {
let source = self.extract_vertex_id_arg(name, args, 0, "source")?;
let target = self.extract_vertex_id_arg(name, args, 1, "target")?;
let max_depth = self.extract_int_arg(name, args, 2, "maxDepth")? as u32;
use crate::traversal::algorithm_steps::iddfs_on_storage;
match iddfs_on_storage(storage, source, target, max_depth) {
Some(path_value) => {
let distance = match &path_value {
Value::List(v) => Value::Int(v.len() as i64 - 1),
_ => Value::Int(0),
};
let mut row = HashMap::new();
self.bind_yield(&mut row, yield_items, "path", path_value);
self.bind_yield(&mut row, yield_items, "distance", distance);
Ok(vec![row])
}
None => Ok(vec![]),
}
}
#[cfg(feature = "full-text")]
"interstellar.searchTextV" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_v(
name,
&property,
crate::storage::text::TextQuery::Match(query),
k,
yield_items,
)
}
#[cfg(feature = "full-text")]
"interstellar.searchTextAllV" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_v(
name,
&property,
crate::storage::text::TextQuery::MatchAll(query),
k,
yield_items,
)
}
#[cfg(feature = "full-text")]
"interstellar.searchTextPhraseV" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_v(
name,
&property,
crate::storage::text::TextQuery::Phrase {
text: query,
slop: 0,
},
k,
yield_items,
)
}
#[cfg(feature = "full-text")]
"interstellar.searchTextPrefixV" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_v(
name,
&property,
crate::storage::text::TextQuery::Prefix(query),
k,
yield_items,
)
}
#[cfg(feature = "full-text")]
"interstellar.searchTextE" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_e(
name,
&property,
crate::storage::text::TextQuery::Match(query),
k,
yield_items,
)
}
#[cfg(feature = "full-text")]
"interstellar.searchTextAllE" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_e(
name,
&property,
crate::storage::text::TextQuery::MatchAll(query),
k,
yield_items,
)
}
#[cfg(feature = "full-text")]
"interstellar.searchTextPhraseE" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_e(
name,
&property,
crate::storage::text::TextQuery::Phrase {
text: query,
slop: 0,
},
k,
yield_items,
)
}
#[cfg(feature = "full-text")]
"interstellar.searchTextPrefixE" => {
let property = self.extract_string_arg(name, args, 0, "property")?;
let query = self.extract_string_arg(name, args, 1, "query")?;
let k = self.extract_int_arg(name, args, 2, "k")?;
self.dispatch_search_text_e(
name,
&property,
crate::storage::text::TextQuery::Prefix(query),
k,
yield_items,
)
}
#[cfg(not(feature = "full-text"))]
"interstellar.searchTextV"
| "interstellar.searchTextAllV"
| "interstellar.searchTextPhraseV"
| "interstellar.searchTextPrefixV"
| "interstellar.searchTextE"
| "interstellar.searchTextAllE"
| "interstellar.searchTextPhraseE"
| "interstellar.searchTextPrefixE" => Err(CompileError::ProcedureArgumentError {
procedure: name.to_string(),
message: "full-text-search procedures require the `full-text` feature".to_string(),
}),
_ => Err(CompileError::UnknownProcedure {
name: name.to_string(),
}),
}
}
fn extract_vertex_id_arg(
&self,
proc_name: &str,
args: &[Value],
index: usize,
param_name: &str,
) -> Result<VertexId, CompileError> {
let value = args
.get(index)
.ok_or_else(|| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("missing argument '{param_name}' at position {index}"),
})?;
match value {
Value::Vertex(id) => Ok(*id),
Value::Int(n) => Ok(VertexId(*n as u64)),
_ => Err(CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("argument '{param_name}' must be a vertex ID, got {value:?}"),
}),
}
}
fn extract_string_arg(
&self,
proc_name: &str,
args: &[Value],
index: usize,
param_name: &str,
) -> Result<String, CompileError> {
let value = args
.get(index)
.ok_or_else(|| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("missing argument '{param_name}' at position {index}"),
})?;
match value {
Value::String(s) => Ok(s.clone()),
_ => Err(CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("argument '{param_name}' must be a string, got {value:?}"),
}),
}
}
fn extract_int_arg(
&self,
proc_name: &str,
args: &[Value],
index: usize,
param_name: &str,
) -> Result<i64, CompileError> {
let value = args
.get(index)
.ok_or_else(|| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("missing argument '{param_name}' at position {index}"),
})?;
match value {
Value::Int(n) => Ok(*n),
_ => Err(CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("argument '{param_name}' must be an integer, got {value:?}"),
}),
}
}
fn bind_yield(
&self,
row: &mut HashMap<String, Value>,
yield_items: &[YieldItem],
field_name: &str,
value: Value,
) {
if yield_items.is_empty() {
row.insert(field_name.to_string(), value);
} else {
for item in yield_items {
if item.field == field_name {
let key = item.alias.as_ref().unwrap_or(&item.field).clone();
row.insert(key, value);
return;
}
}
}
}
#[cfg(feature = "full-text")]
fn yield_requests(&self, yield_items: &[YieldItem], field: &str) -> bool {
yield_items.is_empty() || yield_items.iter().any(|y| y.field == field)
}
#[cfg(feature = "full-text")]
fn materialize_vertex(&self, vid: VertexId) -> Value {
use crate::value::IntoValueMap;
let storage = self.snapshot.storage();
match storage.get_vertex(vid) {
Some(v) => Value::Map(v.properties.into_value_map()),
None => Value::Null,
}
}
#[cfg(feature = "full-text")]
fn materialize_edge(&self, eid: crate::value::EdgeId) -> Value {
use crate::value::IntoValueMap;
let storage = self.snapshot.storage();
match storage.get_edge(eid) {
Some(e) => Value::Map(e.properties.into_value_map()),
None => Value::Null,
}
}
#[cfg(feature = "full-text")]
fn dispatch_search_text_v(
&self,
proc_name: &str,
property: &str,
query: crate::storage::text::TextQuery,
k: i64,
yield_items: &[YieldItem],
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let graph =
self.graph_handle
.as_ref()
.ok_or_else(|| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: "full-text-search procedures require a graph-bound \
GQL entry point (use Graph::gql, not the snapshot-only API)"
.to_string(),
})?;
if property.is_empty() {
return Err(CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: "property name cannot be empty".to_string(),
});
}
if k <= 0 {
return Err(CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("k must be > 0, got {k}"),
});
}
let index =
graph
.text_index_v(property)
.ok_or_else(|| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("no vertex text index registered for property {property:?}"),
})?;
let hits =
index
.search(&query, k as usize)
.map_err(|e| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("text search failed: {e}"),
})?;
let want_elem = self.yield_requests(yield_items, "elem");
let mut rows = Vec::with_capacity(hits.len());
for hit in hits {
let Some(vid) = hit.element.as_vertex() else {
continue; };
let mut row = HashMap::new();
if want_elem {
self.bind_yield(&mut row, yield_items, "elem", self.materialize_vertex(vid));
}
self.bind_yield(&mut row, yield_items, "elemId", Value::Vertex(vid));
self.bind_yield(
&mut row,
yield_items,
"score",
Value::Float(hit.score as f64),
);
rows.push(row);
}
Ok(rows)
}
#[cfg(feature = "full-text")]
fn dispatch_search_text_e(
&self,
proc_name: &str,
property: &str,
query: crate::storage::text::TextQuery,
k: i64,
yield_items: &[YieldItem],
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let graph =
self.graph_handle
.as_ref()
.ok_or_else(|| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: "full-text-search procedures require a graph-bound \
GQL entry point (use Graph::gql, not the snapshot-only API)"
.to_string(),
})?;
if property.is_empty() {
return Err(CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: "property name cannot be empty".to_string(),
});
}
if k <= 0 {
return Err(CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("k must be > 0, got {k}"),
});
}
let index =
graph
.text_index_e(property)
.ok_or_else(|| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("no edge text index registered for property {property:?}"),
})?;
let hits =
index
.search(&query, k as usize)
.map_err(|e| CompileError::ProcedureArgumentError {
procedure: proc_name.to_string(),
message: format!("text search failed: {e}"),
})?;
let want_elem = self.yield_requests(yield_items, "elem");
let mut rows = Vec::with_capacity(hits.len());
for hit in hits {
let Some(eid) = hit.element.as_edge() else {
continue;
};
let mut row = HashMap::new();
if want_elem {
self.bind_yield(&mut row, yield_items, "elem", self.materialize_edge(eid));
}
self.bind_yield(&mut row, yield_items, "elemId", Value::Edge(eid));
self.bind_yield(
&mut row,
yield_items,
"score",
Value::Float(hit.score as f64),
);
rows.push(row);
}
Ok(rows)
}
fn execute_correlated_call(
&self,
outer_rows: Vec<HashMap<String, Value>>,
call_clause: &CallClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let mut result_rows = Vec::new();
for outer_row in outer_rows {
let sub_results = self.execute_call_body_with_context(&call_clause.body, &outer_row)?;
for sub_row in sub_results {
let mut combined = outer_row.clone();
combined.extend(sub_row);
result_rows.push(combined);
}
}
Ok(result_rows)
}
fn execute_uncorrelated_call(
&self,
outer_rows: Vec<HashMap<String, Value>>,
call_clause: &CallClause,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let empty_context = HashMap::new();
let sub_results = self.execute_call_body_with_context(&call_clause.body, &empty_context)?;
if sub_results.is_empty() {
return Ok(Vec::new());
}
let mut result_rows = Vec::new();
for outer_row in outer_rows {
for sub_row in &sub_results {
let mut combined = outer_row.clone();
combined.extend(sub_row.clone());
result_rows.push(combined);
}
}
Ok(result_rows)
}
fn execute_call_body_with_context(
&self,
body: &CallBody,
outer_context: &HashMap<String, Value>,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
match body {
CallBody::Single(query) => self.execute_call_query_with_context(query, outer_context),
CallBody::Union { queries, all } => {
let mut all_results = Vec::new();
for query in queries {
let results = self.execute_call_query_with_context(query, outer_context)?;
all_results.extend(results);
}
if *all {
Ok(all_results)
} else {
Ok(self.deduplicate_rows(all_results))
}
}
}
}
fn execute_call_query_with_context(
&self,
query: &CallQuery,
outer_context: &HashMap<String, Value>,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
let mut scope = HashMap::new();
if let Some(importing_with) = &query.importing_with {
for item in &importing_with.items {
let var_name = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let value = self.evaluate_expression_from_row(&item.expression, outer_context);
scope.insert(var_name, value);
}
}
let mut current_rows: Vec<HashMap<String, Value>> =
if let Some(match_clause) = &query.match_clause {
self.execute_match_for_call(match_clause, &scope)?
} else {
vec![scope]
};
if let Some(where_cl) = &query.where_clause {
current_rows.retain(|row| self.evaluate_predicate_from_row(&where_cl.expression, row));
}
for nested_call in &query.call_clauses {
current_rows = self.execute_call_clause(current_rows, nested_call)?;
}
for with_clause in &query.with_clauses {
current_rows = self.apply_with_clause(current_rows, with_clause)?;
}
if let Some(order_clause) = &query.order_clause {
current_rows = self.apply_order_by_to_rows(current_rows, order_clause)?;
}
if let Some(limit_clause) = &query.limit_clause {
current_rows = self.apply_limit_to_rows(current_rows, limit_clause);
}
let has_aggregates = query
.return_clause
.items
.iter()
.any(|item| Self::expr_has_aggregate(&item.expression));
let result_rows: Vec<HashMap<String, Value>> = if has_aggregates {
if current_rows.is_empty() {
Vec::new()
} else {
let mut result = HashMap::new();
for item in &query.return_clause.items {
let key = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let value = if Self::expr_has_aggregate(&item.expression) {
self.compute_aggregate_over_rows(¤t_rows, &item.expression)
} else {
self.evaluate_expression_from_row(&item.expression, ¤t_rows[0])
};
result.insert(key, value);
}
vec![result]
}
} else {
current_rows
.into_iter()
.map(|row| {
let mut result = HashMap::new();
for item in &query.return_clause.items {
let key = item
.alias
.clone()
.unwrap_or_else(|| Self::expression_to_key(&item.expression));
let value = self.evaluate_expression_from_row(&item.expression, &row);
result.insert(key, value);
}
result
})
.collect()
};
Ok(result_rows)
}
fn execute_match_for_call(
&self,
match_clause: &MatchClause,
imported_scope: &HashMap<String, Value>,
) -> Result<Vec<HashMap<String, Value>>, CompileError> {
use crate::traversal::Traverser;
if match_clause.patterns.is_empty() {
return Ok(vec![imported_scope.clone()]);
}
let pattern = &match_clause.patterns[0];
if pattern.elements.is_empty() {
return Ok(vec![imported_scope.clone()]);
}
let first_node = pattern.elements.first();
let start_vertex = match first_node {
Some(PatternElement::Node(node)) => {
if let Some(var) = &node.variable {
if let Some(Value::Vertex(vid)) = imported_scope.get(var) {
Some(*vid)
} else {
None
}
} else {
None
}
}
_ => None,
};
let g = crate::traversal::GraphTraversalSource::from_snapshot(self.snapshot);
let traversal = if let Some(vid) = start_vertex {
g.v_ids([vid]).with_path()
} else {
g.v().with_path()
};
let traversal = if let Some(PatternElement::Node(node)) = first_node {
let mut t = traversal;
if !node.labels.is_empty() {
let labels: Vec<&str> = node.labels.iter().map(|s| s.as_str()).collect();
t = t.has_label_any(labels);
}
if let Some(var) = &node.variable {
t = t.as_(var);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
t = t.has_value(key.as_str(), val);
}
t
} else {
traversal
};
let traversal = self.compile_remaining_pattern_for_call(pattern, traversal, 1)?;
let traversers: Vec<Traverser> = traversal.execute().collect();
let result_rows: Vec<HashMap<String, Value>> = traversers
.into_iter()
.map(|t| {
let mut row = imported_scope.clone();
for label in t.path.all_labels() {
if let Some(values) = t.path.get(label) {
if let Some(path_value) = values.last() {
row.insert(label.clone(), path_value.to_value());
}
}
}
row.insert("__current__".to_string(), t.value);
row
})
.collect();
Ok(result_rows)
}
fn compile_remaining_pattern_for_call(
&self,
pattern: &Pattern,
mut traversal: BoundTraversal<'a, (), Value>,
start_index: usize,
) -> Result<BoundTraversal<'a, (), Value>, CompileError> {
for element in pattern.elements.iter().skip(start_index) {
match element {
PatternElement::Edge(edge) => {
let labels: Vec<&str> = edge.labels.iter().map(|s| s.as_str()).collect();
traversal = match edge.direction {
EdgeDirection::Outgoing => {
if labels.is_empty() {
traversal.out_e()
} else {
traversal.out_e_labels(&labels)
}
}
EdgeDirection::Incoming => {
if labels.is_empty() {
traversal.in_e()
} else {
traversal.in_e_labels(&labels)
}
}
EdgeDirection::Both => {
if labels.is_empty() {
traversal.both_e()
} else {
traversal.both_e_labels(&labels)
}
}
};
if let Some(var) = &edge.variable {
traversal = traversal.as_(var);
}
traversal = match edge.direction {
EdgeDirection::Outgoing => traversal.in_v(),
EdgeDirection::Incoming => traversal.out_v(),
EdgeDirection::Both => traversal.other_v(),
};
}
PatternElement::Node(node) => {
if !node.labels.is_empty() {
let labels: Vec<&str> = node.labels.iter().map(|s| s.as_str()).collect();
traversal = traversal.has_label_any(labels);
}
if let Some(var) = &node.variable {
traversal = traversal.as_(var);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
}
}
}
Ok(traversal)
}
fn evaluate_expression_from_row(
&self,
expr: &Expression,
row: &HashMap<String, Value>,
) -> Value {
match expr {
Expression::Literal(lit) => lit.clone().into(),
Expression::Variable(var) => row.get(var).cloned().unwrap_or(Value::Null),
Expression::Parameter(name) => {
self.parameters.get(name).cloned().unwrap_or(Value::Null)
}
Expression::Property { variable, property } => {
let element = row.get(variable).cloned().unwrap_or(Value::Null);
self.extract_property(&element, property)
.unwrap_or(Value::Null)
}
Expression::BinaryOp { left, op, right } => {
let left_val = self.evaluate_expression_from_row(left, row);
let right_val = self.evaluate_expression_from_row(right, row);
apply_binary_op(*op, left_val, right_val)
}
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => {
let val = self.evaluate_expression_from_row(expr, row);
match val {
Value::Bool(b) => Value::Bool(!b),
_ => Value::Null,
}
}
UnaryOperator::Neg => {
let val = self.evaluate_expression_from_row(expr, row);
match val {
Value::Int(n) => Value::Int(-n),
Value::Float(f) => Value::Float(-f),
_ => Value::Null,
}
}
},
Expression::List(items) => {
let values: Vec<Value> = items
.iter()
.map(|item| self.evaluate_expression_from_row(item, row))
.collect();
Value::List(values)
}
Expression::Map(entries) => {
let map: ValueMap = entries
.iter()
.map(|(key, value_expr)| {
let value = self.evaluate_expression_from_row(value_expr, row);
(key.clone(), value)
})
.collect();
Value::Map(map)
}
Expression::FunctionCall { name, args } => {
self.evaluate_function_call_from_row(name, args, row)
}
Expression::ListComprehension {
variable,
list,
filter,
transform,
} => self.evaluate_list_comprehension_from_row(variable, list, filter, transform, row),
Expression::Reduce {
accumulator,
initial,
variable,
list,
expression,
} => {
self.evaluate_reduce_from_row(accumulator, initial, variable, list, expression, row)
}
Expression::Case(case_expr) => self.evaluate_case_from_row(case_expr, row),
Expression::All {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_row(
ListPredicateKind::All,
variable,
list,
condition,
row,
),
Expression::Any {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_row(
ListPredicateKind::Any,
variable,
list,
condition,
row,
),
Expression::None {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_row(
ListPredicateKind::None,
variable,
list,
condition,
row,
),
Expression::Single {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_row(
ListPredicateKind::Single,
variable,
list,
condition,
row,
),
Expression::PatternComprehension {
pattern,
filter,
transform,
} => self.evaluate_pattern_comprehension_from_row(
pattern,
filter.as_deref(),
transform,
row,
),
_ => Value::Null,
}
}
fn evaluate_function_call_from_row(
&self,
name: &str,
args: &[Expression],
row: &HashMap<String, Value>,
) -> Value {
match name.to_uppercase().as_str() {
"COALESCE" => {
for arg in args {
let val = self.evaluate_expression_from_row(arg, row);
if !matches!(val, Value::Null) {
return val;
}
}
Value::Null
}
"TOUPPER" | "UPPER" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_expression_from_row(arg, row) {
return Value::String(s.to_uppercase());
}
}
Value::Null
}
"TOLOWER" | "LOWER" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_expression_from_row(arg, row) {
return Value::String(s.to_lowercase());
}
}
Value::Null
}
"SIZE" | "LENGTH" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::String(s) => Value::Int(s.len() as i64),
Value::List(l) => Value::Int(l.len() as i64),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ABS" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Int(n.abs()),
Value::Float(f) => Value::Float(f.abs()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"SQRT" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) if n >= 0 => Value::Float((n as f64).sqrt()),
Value::Float(f) if f >= 0.0 => Value::Float(f.sqrt()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"POW" | "POWER" => {
if args.len() >= 2 {
let base = self.evaluate_expression_from_row(&args[0], row);
let exp = self.evaluate_expression_from_row(&args[1], row);
apply_binary_op(BinaryOperator::Pow, base, exp)
} else {
Value::Null
}
}
"LOG" | "LN" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) if n > 0 => Value::Float((n as f64).ln()),
Value::Float(f) if f > 0.0 => Value::Float(f.ln()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"LOG10" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) if n > 0 => Value::Float((n as f64).log10()),
Value::Float(f) if f > 0.0 => Value::Float(f.log10()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"EXP" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Float((n as f64).exp()),
Value::Float(f) => Value::Float(f.exp()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"SIN" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Float((n as f64).sin()),
Value::Float(f) => Value::Float(f.sin()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"COS" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Float((n as f64).cos()),
Value::Float(f) => Value::Float(f.cos()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TAN" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Float((n as f64).tan()),
Value::Float(f) => Value::Float(f.tan()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ASIN" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => {
let f = n as f64;
if (-1.0..=1.0).contains(&f) {
Value::Float(f.asin())
} else {
Value::Null
}
}
Value::Float(f) if (-1.0..=1.0).contains(&f) => Value::Float(f.asin()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ACOS" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => {
let f = n as f64;
if (-1.0..=1.0).contains(&f) {
Value::Float(f.acos())
} else {
Value::Null
}
}
Value::Float(f) if (-1.0..=1.0).contains(&f) => Value::Float(f.acos()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ATAN" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Float((n as f64).atan()),
Value::Float(f) => Value::Float(f.atan()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ATAN2" => {
if args.len() >= 2 {
let y = self.evaluate_expression_from_row(&args[0], row);
let x = self.evaluate_expression_from_row(&args[1], row);
match (y, x) {
(Value::Int(y), Value::Int(x)) => Value::Float((y as f64).atan2(x as f64)),
(Value::Float(y), Value::Float(x)) => Value::Float(y.atan2(x)),
(Value::Int(y), Value::Float(x)) => Value::Float((y as f64).atan2(x)),
(Value::Float(y), Value::Int(x)) => Value::Float(y.atan2(x as f64)),
_ => Value::Null,
}
} else {
Value::Null
}
}
"RADIANS" | "TORADIANS" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Float((n as f64).to_radians()),
Value::Float(f) => Value::Float(f.to_radians()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"DEGREES" | "TODEGREES" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Float((n as f64).to_degrees()),
Value::Float(f) => Value::Float(f.to_degrees()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"CEIL" | "CEILING" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Float(f) => Value::Float(f.ceil()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"FLOOR" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Float(f) => Value::Float(f.floor()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ROUND" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Float(f) => Value::Float(f.round()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"SIGN" => {
if let Some(arg) = args.first() {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => Value::Int(n.signum()),
Value::Float(f) => {
if f > 0.0 {
Value::Int(1)
} else if f < 0.0 {
Value::Int(-1)
} else {
Value::Int(0)
}
}
_ => Value::Null,
}
} else {
Value::Null
}
}
"PI" => Value::Float(std::f64::consts::PI),
"E" => Value::Float(std::f64::consts::E),
"MATH" => self.evaluate_math_from_row(args, row),
"PATH" => row.get("__path__").cloned().unwrap_or(Value::List(vec![])),
"PROPERTIES" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_expression_from_row(arg, row);
match element_val {
Value::Vertex(vid) => {
if let Some(vertex) = self.snapshot.storage().get_vertex(vid) {
return Value::Map(vertex.properties.into_value_map());
}
}
Value::Edge(eid) => {
if let Some(edge) = self.snapshot.storage().get_edge(eid) {
return Value::Map(edge.properties.into_value_map());
}
}
_ => {}
}
}
Value::Null
}
"LABELS" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_expression_from_row(arg, row);
if let Value::Vertex(vid) = element_val {
if let Some(vertex) = self.snapshot.storage().get_vertex(vid) {
return Value::List(vec![Value::String(vertex.label)]);
}
}
}
Value::Null
}
"TYPE" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_expression_from_row(arg, row);
if let Value::Edge(eid) = element_val {
if let Some(edge) = self.snapshot.storage().get_edge(eid) {
return Value::String(edge.label);
}
}
}
Value::Null
}
"ID" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_expression_from_row(arg, row);
match element_val {
Value::Vertex(vid) => return id_to_value(vid.0),
Value::Edge(eid) => return id_to_value(eid.0),
_ => {}
}
}
Value::Null
}
_ => Value::Null,
}
}
fn evaluate_list_comprehension_from_row(
&self,
variable: &str,
list_expr: &Expression,
filter: &Option<Box<Expression>>,
transform: &Expression,
row: &HashMap<String, Value>,
) -> Value {
let list_value = self.evaluate_expression_from_row(list_expr, row);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null, };
let mut results = Vec::new();
for item in items {
let mut comp_row = row.clone();
comp_row.insert(variable.to_string(), item);
if let Some(filter_expr) = filter {
if !self.evaluate_predicate_from_row(filter_expr, &comp_row) {
continue; }
}
let transformed = self.evaluate_expression_from_row(transform, &comp_row);
results.push(transformed);
}
Value::List(results)
}
fn evaluate_reduce_from_row(
&self,
accumulator: &str,
initial: &Expression,
variable: &str,
list_expr: &Expression,
expression: &Expression,
row: &HashMap<String, Value>,
) -> Value {
let mut acc_value = self.evaluate_expression_from_row(initial, row);
let list_value = self.evaluate_expression_from_row(list_expr, row);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null, };
for item in items {
let mut iter_row = row.clone();
iter_row.insert(accumulator.to_string(), acc_value);
iter_row.insert(variable.to_string(), item);
acc_value = self.evaluate_expression_from_row(expression, &iter_row);
}
acc_value
}
fn evaluate_list_predicate_from_row(
&self,
kind: ListPredicateKind,
variable: &str,
list_expr: &Expression,
condition: &Expression,
row: &HashMap<String, Value>,
) -> Value {
let list_value = self.evaluate_expression_from_row(list_expr, row);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let mut match_count = 0;
for item in items {
let mut iter_row = row.clone();
iter_row.insert(variable.to_string(), item);
if self.evaluate_predicate_from_row(condition, &iter_row) {
match_count += 1;
if kind == ListPredicateKind::Any {
return Value::Bool(true);
}
if kind == ListPredicateKind::None {
return Value::Bool(false);
}
if kind == ListPredicateKind::Single && match_count > 1 {
return Value::Bool(false);
}
} else {
if kind == ListPredicateKind::All {
return Value::Bool(false);
}
}
}
Value::Bool(match kind {
ListPredicateKind::All => true, ListPredicateKind::Any => false, ListPredicateKind::None => true, ListPredicateKind::Single => match_count == 1,
})
}
fn evaluate_pattern_comprehension_from_row(
&self,
pattern: &Pattern,
filter: Option<&Expression>,
transform: &Expression,
row: &HashMap<String, Value>,
) -> Value {
let start_vertex_id = match pattern.elements.first() {
Some(PatternElement::Node(node)) => {
if let Some(var) = &node.variable {
match row.get(var) {
Some(Value::Vertex(vid)) => *vid,
Some(Value::Map(map)) => {
match map.get("id") {
Some(Value::Vertex(vid)) => *vid,
Some(Value::Int(id)) => VertexId(*id as u64),
_ => return Value::List(vec![]),
}
}
_ => return Value::List(vec![]), }
} else {
return Value::List(vec![]); }
}
_ => return Value::List(vec![]), };
let g = crate::traversal::GraphTraversalSource::from_snapshot(self.snapshot);
let traversal = g.v_ids([start_vertex_id]).with_path();
let traversal = match self.compile_pattern_elements_for_comprehension(pattern, traversal) {
Ok(t) => t,
Err(_) => return Value::List(vec![]),
};
let matches: Vec<Value> = traversal.to_list();
let mut results = Vec::new();
for match_value in matches {
let mut match_row = row.clone();
self.bind_pattern_variables_from_match(pattern, &match_value, &mut match_row);
if let Some(filter_expr) = filter {
if !self.evaluate_predicate_from_row(filter_expr, &match_row) {
continue; }
}
let transformed = self.evaluate_expression_from_row(transform, &match_row);
results.push(transformed);
}
Value::List(results)
}
fn compile_pattern_elements_for_comprehension(
&self,
pattern: &Pattern,
mut traversal: BoundTraversal<'a, (), Value>,
) -> Result<BoundTraversal<'a, (), Value>, CompileError> {
for (idx, element) in pattern.elements.iter().enumerate() {
match element {
PatternElement::Node(node) => {
if idx == 0 {
if !node.labels.is_empty() {
let labels: Vec<&str> =
node.labels.iter().map(|s| s.as_str()).collect();
traversal = traversal.has_label_any(labels);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
} else {
if !node.labels.is_empty() {
let labels: Vec<&str> =
node.labels.iter().map(|s| s.as_str()).collect();
traversal = traversal.has_label_any(labels);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
if let Some(var) = &node.variable {
traversal = traversal.as_(var);
}
}
}
PatternElement::Edge(edge) => {
let labels: Vec<&str> = edge.labels.iter().map(|s| s.as_str()).collect();
traversal = match edge.direction {
EdgeDirection::Outgoing => {
if labels.is_empty() {
traversal.out()
} else {
traversal.out_labels(&labels)
}
}
EdgeDirection::Incoming => {
if labels.is_empty() {
traversal.in_()
} else {
traversal.in_labels(&labels)
}
}
EdgeDirection::Both => {
if labels.is_empty() {
traversal.both()
} else {
traversal.both_labels(&labels)
}
}
};
}
}
}
Ok(traversal)
}
fn bind_pattern_variables_from_match(
&self,
pattern: &Pattern,
match_value: &Value,
row: &mut HashMap<String, Value>,
) {
if let Some(PatternElement::Node(last_node)) = pattern.elements.last() {
if let Some(var) = &last_node.variable {
row.insert(var.clone(), match_value.clone());
}
}
if let Value::Map(map) = match_value {
for (key, value) in map {
if !row.contains_key(key) {
row.insert(key.clone(), value.clone());
}
}
}
}
fn evaluate_case_from_row(
&self,
case_expr: &CaseExpression,
row: &HashMap<String, Value>,
) -> Value {
for (condition, result) in &case_expr.when_clauses {
if self.evaluate_predicate_from_row(condition, row) {
return self.evaluate_expression_from_row(result, row);
}
}
if let Some(else_expr) = &case_expr.else_clause {
self.evaluate_expression_from_row(else_expr, row)
} else {
Value::Null
}
}
fn evaluate_predicate_from_row(&self, expr: &Expression, row: &HashMap<String, Value>) -> bool {
match expr {
Expression::BinaryOp { left, op, right } => match op {
BinaryOperator::And => {
self.evaluate_predicate_from_row(left, row)
&& self.evaluate_predicate_from_row(right, row)
}
BinaryOperator::Or => {
self.evaluate_predicate_from_row(left, row)
|| self.evaluate_predicate_from_row(right, row)
}
_ => {
let left_val = self.evaluate_expression_from_row(left, row);
let right_val = self.evaluate_expression_from_row(right, row);
apply_comparison(*op, &left_val, &right_val)
}
},
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => !self.evaluate_predicate_from_row(expr, row),
UnaryOperator::Neg => match self.evaluate_expression_from_row(expr, row) {
Value::Int(n) => n == 0,
Value::Float(f) => f == 0.0,
Value::Bool(b) => !b,
Value::Null => true,
_ => false,
},
},
Expression::IsNull { expr, negated } => {
let val = self.evaluate_expression_from_row(expr, row);
let is_null = matches!(val, Value::Null);
if *negated {
!is_null
} else {
is_null
}
}
Expression::InList {
expr,
list,
negated,
} => {
let val = self.evaluate_expression_from_row(expr, row);
let in_list = list.iter().any(|item| {
let item_val = self.evaluate_expression_from_row(item, row);
val == item_val
});
if *negated {
!in_list
} else {
in_list
}
}
_ => {
let val = self.evaluate_expression_from_row(expr, row);
match val {
Value::Bool(b) => b,
Value::Null => false,
Value::Int(n) => n != 0,
Value::Float(f) => f != 0.0,
Value::String(s) => !s.is_empty(),
_ => true,
}
}
}
}
fn evaluate_return_for_row(
&self,
items: &[ReturnItem],
row: &HashMap<String, Value>,
) -> Option<Value> {
if items.len() == 1 {
Some(self.evaluate_expression_from_row(&items[0].expression, row))
} else {
let mut map = ValueMap::new();
for item in items {
let key = self.get_return_item_key(item);
let value = self.evaluate_expression_from_row(&item.expression, row);
map.insert(key, value);
}
Some(Value::Map(map))
}
}
fn register_optional_pattern_variables(&mut self, pattern: &Pattern) {
for (index, element) in pattern.elements.iter().enumerate() {
match element {
PatternElement::Node(node) => {
if let Some(var) = &node.variable {
if !self.bindings.contains_key(var) {
self.bindings.insert(
var.clone(),
BindingInfo {
pattern_index: index,
is_node: true,
},
);
}
}
}
PatternElement::Edge(edge) => {
if let Some(var) = &edge.variable {
if !self.bindings.contains_key(var) {
self.bindings.insert(
var.clone(),
BindingInfo {
pattern_index: index,
is_node: false,
},
);
}
}
}
}
}
}
fn compile_pattern(
&mut self,
pattern: &Pattern,
mut traversal: BoundTraversal<'a, (), Value>,
) -> Result<BoundTraversal<'a, (), Value>, CompileError> {
for (element_index, element) in pattern.elements.iter().enumerate() {
match element {
PatternElement::Node(node) => {
traversal = self.compile_node(node, traversal, element_index)?;
}
PatternElement::Edge(edge) => {
traversal = self.compile_edge(edge, traversal)?;
}
}
}
Ok(traversal)
}
fn compile_node(
&mut self,
node: &NodePattern,
mut traversal: BoundTraversal<'a, (), Value>,
index: usize,
) -> Result<BoundTraversal<'a, (), Value>, CompileError> {
if !node.labels.is_empty() {
let labels: Vec<&str> = node.labels.iter().map(|s| s.as_str()).collect();
traversal = traversal.has_label_any(labels);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
if let Some(where_expr) = &node.where_clause {
let expr = where_expr.clone();
let params = self.parameters.clone();
traversal = traversal
.filter(move |ctx, val| eval_inline_predicate(ctx.storage(), &expr, val, ¶ms));
}
if let Some(var) = &node.variable {
if self.bindings.contains_key(var) {
return Err(CompileError::duplicate_variable(var));
}
self.bindings.insert(
var.clone(),
BindingInfo {
pattern_index: index,
is_node: true,
},
);
if self.has_multi_vars {
traversal = traversal.as_(var);
}
}
Ok(traversal)
}
fn compile_edge(
&mut self,
edge: &EdgePattern,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<BoundTraversal<'a, (), Value>, CompileError> {
if let Some(quantifier) = &edge.quantifier {
return self.compile_edge_with_quantifier(edge, quantifier, traversal);
}
let needs_edge_access =
edge.variable.is_some() || !edge.properties.is_empty() || edge.where_clause.is_some();
if needs_edge_access {
return self.compile_edge_with_variable(edge, traversal);
}
let labels: Vec<&str> = edge.labels.iter().map(|s| s.as_str()).collect();
let traversal = match edge.direction {
EdgeDirection::Outgoing => {
if labels.is_empty() {
traversal.out()
} else {
traversal.out_labels(&labels)
}
}
EdgeDirection::Incoming => {
if labels.is_empty() {
traversal.in_()
} else {
traversal.in_labels(&labels)
}
}
EdgeDirection::Both => {
if labels.is_empty() {
traversal.both()
} else {
traversal.both_labels(&labels)
}
}
};
Ok(traversal)
}
fn compile_edge_with_variable(
&mut self,
edge: &EdgePattern,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<BoundTraversal<'a, (), Value>, CompileError> {
let labels: Vec<&str> = edge.labels.iter().map(|s| s.as_str()).collect();
let mut traversal = match edge.direction {
EdgeDirection::Outgoing => {
if labels.is_empty() {
traversal.out_e()
} else {
traversal.out_e_labels(&labels)
}
}
EdgeDirection::Incoming => {
if labels.is_empty() {
traversal.in_e()
} else {
traversal.in_e_labels(&labels)
}
}
EdgeDirection::Both => {
if labels.is_empty() {
traversal.both_e()
} else {
traversal.both_e_labels(&labels)
}
}
};
for (key, value) in &edge.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
if let Some(where_expr) = &edge.where_clause {
let expr = where_expr.clone();
let params = self.parameters.clone();
traversal = traversal
.filter(move |ctx, val| eval_inline_predicate(ctx.storage(), &expr, val, ¶ms));
}
if let Some(var) = &edge.variable {
if self.bindings.contains_key(var) {
return Err(CompileError::duplicate_variable(var));
}
self.bindings.insert(
var.clone(),
BindingInfo {
pattern_index: 0, is_node: false,
},
);
if self.has_multi_vars {
traversal = traversal.as_(var);
}
}
let traversal = match edge.direction {
EdgeDirection::Outgoing => traversal.in_v(),
EdgeDirection::Incoming => traversal.out_v(),
EdgeDirection::Both => traversal.other_v(),
};
Ok(traversal)
}
fn build_edge_sub_traversal(
&self,
direction: EdgeDirection,
labels: &[String],
) -> Traversal<Value, Value> {
let label_refs: Vec<&str> = labels.iter().map(|s| s.as_str()).collect();
match direction {
EdgeDirection::Outgoing => {
if label_refs.is_empty() {
__.out()
} else {
__.out_labels(&label_refs)
}
}
EdgeDirection::Incoming => {
if label_refs.is_empty() {
__.in_()
} else {
__.in_labels(&label_refs)
}
}
EdgeDirection::Both => {
if label_refs.is_empty() {
__.both()
} else {
__.both_labels(&label_refs)
}
}
}
}
fn compile_edge_with_quantifier(
&mut self,
edge: &EdgePattern,
quantifier: &PathQuantifier,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<BoundTraversal<'a, (), Value>, CompileError> {
let sub = self.build_edge_sub_traversal(edge.direction, &edge.labels);
const DEFAULT_MAX: usize = 10;
let min = quantifier.min.map(|v| v as usize);
let max = quantifier.max.map(|v| v as usize);
let skip_dedup = self.has_multi_vars;
let traversal = match (min, max) {
(Some(m), Some(n)) if m == n => {
let t = traversal.repeat(sub).times(n);
if skip_dedup {
t.identity()
} else {
t.dedup()
}
}
(Some(m), Some(n)) => {
if m == 0 {
let t = traversal.repeat(sub).times(n).emit().emit_first();
if skip_dedup {
t.identity()
} else {
t.dedup()
}
} else {
let t = traversal.repeat(sub).times(n).emit();
if skip_dedup {
t.identity()
} else {
t.dedup()
}
}
}
(None, Some(n)) => {
let t = traversal.repeat(sub).times(n).emit().emit_first();
if skip_dedup {
t.identity()
} else {
t.dedup()
}
}
(Some(m), None) => {
if m == 0 {
let t = traversal.repeat(sub).times(DEFAULT_MAX).emit().emit_first();
if skip_dedup {
t.identity()
} else {
t.dedup()
}
} else {
let t = traversal.repeat(sub).times(DEFAULT_MAX).emit();
if skip_dedup {
t.identity()
} else {
t.dedup()
}
}
}
(None, None) => {
let t = traversal.repeat(sub).times(DEFAULT_MAX).emit().emit_first();
if skip_dedup {
t.identity()
} else {
t.dedup()
}
}
};
Ok(traversal)
}
fn execute_return(
&self,
return_clause: &ReturnClause,
where_clause: &Option<WhereClause>,
having_clause: &Option<HavingClause>,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
for item in &return_clause.items {
self.validate_expression_variables(&item.expression)?;
}
if let Some(where_cl) = where_clause {
self.validate_expression_variables(&where_cl.expression)?;
}
if self.has_aggregates(return_clause) {
return self.execute_aggregated_return(
return_clause,
where_clause,
having_clause,
traversal,
);
}
if self.has_multi_vars {
return self.execute_multi_var_return(return_clause, where_clause, traversal);
}
let matched_elements: Vec<Value> = traversal.to_list();
let filtered_elements = if let Some(where_cl) = where_clause {
matched_elements
.into_iter()
.filter(|element| self.evaluate_predicate(&where_cl.expression, element))
.collect()
} else {
matched_elements
};
let results: Vec<Value> = filtered_elements
.into_iter()
.filter_map(|element| self.evaluate_return_for_element(&return_clause.items, &element))
.collect();
let results = if return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
Ok(results)
}
fn execute_multi_var_return(
&self,
return_clause: &ReturnClause,
where_clause: &Option<WhereClause>,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
let traversers: Vec<Traverser> = traversal.execute().collect();
let filtered_traversers: Vec<Traverser> = if let Some(where_cl) = where_clause {
traversers
.into_iter()
.filter(|t| self.evaluate_predicate_from_path(&where_cl.expression, t))
.collect()
} else {
traversers
};
let results: Vec<Value> = filtered_traversers
.into_iter()
.filter_map(|t| self.evaluate_return_for_traverser(&return_clause.items, &t))
.collect();
let results = if return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
Ok(results)
}
fn execute_with_optional_match(
&self,
return_clause: &ReturnClause,
where_clause: &Option<WhereClause>,
let_clauses: &[LetClause],
optional_match_clauses: &[OptionalMatchClause],
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
let base_traversers: Vec<Traverser> = traversal.execute().collect();
let mut expanded_traversers: Vec<Traverser> = Vec::new();
for base_traverser in base_traversers {
let mut current_traversers = vec![base_traverser];
for opt_clause in optional_match_clauses {
let mut next_traversers = Vec::new();
for traverser in current_traversers {
let optional_results = self.try_optional_match(opt_clause, &traverser)?;
if optional_results.is_empty() {
let mut updated_traverser = traverser.clone();
self.add_null_optional_vars(&mut updated_traverser.path, opt_clause);
next_traversers.push(updated_traverser);
} else {
for opt_result in optional_results {
let mut merged_traverser = traverser.clone();
self.merge_paths(&mut merged_traverser.path, &opt_result.path);
merged_traverser.value = opt_result.value;
next_traversers.push(merged_traverser);
}
}
}
current_traversers = next_traversers;
}
expanded_traversers.extend(current_traversers);
}
let filtered_traversers: Vec<Traverser> = if let Some(where_cl) = where_clause {
expanded_traversers
.into_iter()
.filter(|t| self.evaluate_predicate_from_path(&where_cl.expression, t))
.collect()
} else {
expanded_traversers
};
if !let_clauses.is_empty() {
let current_rows: Vec<HashMap<String, Value>> = filtered_traversers
.into_iter()
.map(|t| {
let mut row = HashMap::new();
for label in t.path.all_labels() {
if let Some(values) = t.path.get(label) {
if let Some(path_value) = values.last() {
row.insert(label.clone(), path_value.to_value());
}
}
}
let path_values: Vec<Value> =
t.path.objects().map(|pv| pv.to_value()).collect();
row.insert("__path__".to_string(), Value::List(path_values));
row.insert("__current__".to_string(), t.value);
row
})
.collect();
let rows_with_let = self.apply_let_clauses(current_rows, let_clauses);
let results: Vec<Value> = rows_with_let
.into_iter()
.filter_map(|row| self.evaluate_return_for_row(&return_clause.items, &row))
.collect();
let results = if return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
return Ok(results);
}
let results: Vec<Value> = filtered_traversers
.into_iter()
.filter_map(|t| self.evaluate_return_for_traverser(&return_clause.items, &t))
.collect();
let results = if return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
Ok(results)
}
fn try_optional_match(
&self,
opt_clause: &OptionalMatchClause,
base_traverser: &crate::traversal::Traverser,
) -> Result<Vec<crate::traversal::Traverser>, CompileError> {
use crate::traversal::Traverser;
if opt_clause.patterns.is_empty() {
return Ok(vec![]);
}
let pattern = &opt_clause.patterns[0];
if pattern.elements.is_empty() {
return Ok(vec![]);
}
let anchor_var = match &pattern.elements[0] {
PatternElement::Node(node) => node.variable.as_ref(),
PatternElement::Edge(_) => None,
};
let anchor_vertex_id = if let Some(var) = anchor_var {
if let Some(values) = base_traverser.path.get(var) {
if let Some(path_value) = values.last() {
match path_value.to_value() {
Value::Vertex(vid) => Some(vid),
_ => None,
}
} else {
None
}
} else {
match &base_traverser.value {
Value::Vertex(vid) => Some(*vid),
_ => None,
}
}
} else {
match &base_traverser.value {
Value::Vertex(vid) => Some(*vid),
_ => None,
}
};
let anchor_id = match anchor_vertex_id {
Some(id) => id,
None => return Ok(vec![]), };
let g = crate::traversal::GraphTraversalSource::from_snapshot(self.snapshot);
let mut traversal = g.v_ids([anchor_id]).with_path();
let mut skip_first_node = anchor_var.is_some();
for element in &pattern.elements {
match element {
PatternElement::Node(node) => {
if skip_first_node {
if !node.labels.is_empty() {
let labels: Vec<&str> =
node.labels.iter().map(|s| s.as_str()).collect();
traversal = traversal.has_label_any(labels);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
if let Some(var) = &node.variable {
traversal = traversal.as_(var);
}
skip_first_node = false;
} else {
if !node.labels.is_empty() {
let labels: Vec<&str> =
node.labels.iter().map(|s| s.as_str()).collect();
traversal = traversal.has_label_any(labels);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
if let Some(var) = &node.variable {
traversal = traversal.as_(var);
}
}
}
PatternElement::Edge(edge) => {
let labels: Vec<&str> = edge.labels.iter().map(|s| s.as_str()).collect();
traversal = match edge.direction {
EdgeDirection::Outgoing => {
if labels.is_empty() {
traversal.out()
} else {
traversal.out_labels(&labels)
}
}
EdgeDirection::Incoming => {
if labels.is_empty() {
traversal.in_()
} else {
traversal.in_labels(&labels)
}
}
EdgeDirection::Both => {
if labels.is_empty() {
traversal.both()
} else {
traversal.both_labels(&labels)
}
}
};
}
}
}
let results: Vec<Traverser> = traversal.execute().collect();
Ok(results)
}
fn add_null_optional_vars(
&self,
path: &mut crate::traversal::Path,
opt_clause: &OptionalMatchClause,
) {
use crate::traversal::PathValue;
for pattern in &opt_clause.patterns {
for element in &pattern.elements {
match element {
PatternElement::Node(node) => {
if let Some(var) = &node.variable {
if path.get(var).is_none() {
path.push_labeled(PathValue::Property(Value::Null), var);
}
}
}
PatternElement::Edge(edge) => {
if let Some(var) = &edge.variable {
if path.get(var).is_none() {
path.push_labeled(PathValue::Property(Value::Null), var);
}
}
}
}
}
}
}
fn merge_paths(
&self,
base_path: &mut crate::traversal::Path,
optional_path: &crate::traversal::Path,
) {
for label in optional_path.all_labels() {
if let Some(values) = optional_path.get(label) {
for value in values {
base_path.push_labeled(value.clone(), label);
}
}
}
}
fn evaluate_predicate_from_path(
&self,
expr: &Expression,
traverser: &crate::traversal::Traverser,
) -> bool {
match expr {
Expression::BinaryOp { left, op, right } => {
match op {
BinaryOperator::And => {
self.evaluate_predicate_from_path(left, traverser)
&& self.evaluate_predicate_from_path(right, traverser)
}
BinaryOperator::Or => {
self.evaluate_predicate_from_path(left, traverser)
|| self.evaluate_predicate_from_path(right, traverser)
}
_ => {
let left_val = self.evaluate_value_from_path(left, traverser);
let right_val = self.evaluate_value_from_path(right, traverser);
apply_comparison(*op, &left_val, &right_val)
}
}
}
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => !self.evaluate_predicate_from_path(expr, traverser),
UnaryOperator::Neg => match self.evaluate_value_from_path(expr, traverser) {
Value::Int(n) => n == 0,
Value::Float(f) => f == 0.0,
Value::Bool(b) => !b,
Value::Null => true,
_ => false,
},
},
Expression::IsNull { expr, negated } => {
let val = self.evaluate_value_from_path(expr, traverser);
let is_null = matches!(val, Value::Null);
if *negated {
!is_null
} else {
is_null
}
}
Expression::InList {
expr,
list,
negated,
} => {
let val = self.evaluate_value_from_path(expr, traverser);
let in_list = list.iter().any(|item| {
let item_val = self.evaluate_value_from_path(item, traverser);
val == item_val
});
if *negated {
!in_list
} else {
in_list
}
}
Expression::Exists {
pattern,
negated,
where_expr,
} => {
let exists = self.evaluate_exists_pattern_with_where(
pattern,
where_expr.as_deref(),
&traverser.value,
);
if *negated {
!exists
} else {
exists
}
}
_ => {
let val = self.evaluate_value_from_path(expr, traverser);
match val {
Value::Bool(b) => b,
Value::Null => false,
Value::Int(n) => n != 0,
Value::Float(f) => f != 0.0,
Value::String(s) => !s.is_empty(),
_ => true,
}
}
}
}
fn evaluate_value_from_path(
&self,
expr: &Expression,
traverser: &crate::traversal::Traverser,
) -> Value {
match expr {
Expression::Literal(lit) => lit.clone().into(),
Expression::Variable(var) => {
self.get_variable_value_from_path(var, traverser)
}
Expression::Parameter(name) => {
self.parameters.get(name).cloned().unwrap_or(Value::Null)
}
Expression::Property { variable, property } => {
let element = self.get_variable_value_from_path(variable, traverser);
self.extract_property(&element, property)
.unwrap_or(Value::Null)
}
Expression::BinaryOp { left, op, right } => {
let left_val = self.evaluate_value_from_path(left, traverser);
let right_val = self.evaluate_value_from_path(right, traverser);
apply_binary_op(*op, left_val, right_val)
}
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => {
let val = self.evaluate_value_from_path(expr, traverser);
match val {
Value::Bool(b) => Value::Bool(!b),
_ => Value::Null,
}
}
UnaryOperator::Neg => {
let val = self.evaluate_value_from_path(expr, traverser);
match val {
Value::Int(n) => Value::Int(-n),
Value::Float(f) => Value::Float(-f),
_ => Value::Null,
}
}
},
Expression::IsNull { expr, negated } => {
let val = self.evaluate_value_from_path(expr, traverser);
let is_null = matches!(val, Value::Null);
Value::Bool(if *negated { !is_null } else { is_null })
}
Expression::InList {
expr,
list,
negated,
} => {
let val = self.evaluate_value_from_path(expr, traverser);
let in_list = list.iter().any(|item| {
let item_val = self.evaluate_value_from_path(item, traverser);
val == item_val
});
Value::Bool(if *negated { !in_list } else { in_list })
}
Expression::List(items) => {
let values: Vec<Value> = items
.iter()
.map(|item| self.evaluate_value_from_path(item, traverser))
.collect();
Value::List(values)
}
Expression::Map(entries) => {
let map: ValueMap = entries
.iter()
.map(|(key, value_expr)| {
let value = self.evaluate_value_from_path(value_expr, traverser);
(key.clone(), value)
})
.collect();
Value::Map(map)
}
Expression::Exists {
pattern,
negated,
where_expr,
} => {
let exists = self.evaluate_exists_pattern_with_where(
pattern,
where_expr.as_deref(),
&traverser.value,
);
Value::Bool(if *negated { !exists } else { exists })
}
Expression::FunctionCall { name, args } => {
self.evaluate_function_call_from_path(name, args, traverser)
}
Expression::Case(case_expr) => self.evaluate_case_from_path(case_expr, traverser),
Expression::Reduce {
accumulator,
initial,
variable,
list,
expression,
} => self.evaluate_reduce_from_path(
accumulator,
initial,
variable,
list,
expression,
traverser,
),
Expression::ListComprehension {
variable,
list,
filter,
transform,
} => self.evaluate_list_comprehension_from_path(
variable, list, filter, transform, traverser,
),
Expression::All {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_path(
ListPredicateKind::All,
variable,
list,
condition,
traverser,
),
Expression::Any {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_path(
ListPredicateKind::Any,
variable,
list,
condition,
traverser,
),
Expression::None {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_path(
ListPredicateKind::None,
variable,
list,
condition,
traverser,
),
Expression::Single {
variable,
list,
condition,
} => self.evaluate_list_predicate_from_path(
ListPredicateKind::Single,
variable,
list,
condition,
traverser,
),
_ => Value::Null,
}
}
fn evaluate_reduce_from_path(
&self,
accumulator: &str,
initial: &Expression,
variable: &str,
list_expr: &Expression,
expression: &Expression,
traverser: &crate::traversal::Traverser,
) -> Value {
let mut acc_value = self.evaluate_value_from_path(initial, traverser);
let list_value = self.evaluate_value_from_path(list_expr, traverser);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
for item in items {
let mut iter_map = std::collections::HashMap::new();
iter_map.insert(accumulator.to_string(), acc_value);
iter_map.insert(variable.to_string(), item);
acc_value = self.evaluate_expression_from_row(expression, &iter_map);
}
acc_value
}
fn evaluate_list_predicate_from_path(
&self,
kind: ListPredicateKind,
variable: &str,
list_expr: &Expression,
condition: &Expression,
traverser: &crate::traversal::Traverser,
) -> Value {
let list_value = self.evaluate_value_from_path(list_expr, traverser);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let mut match_count = 0;
for item in items {
let mut iter_map = std::collections::HashMap::new();
iter_map.insert(variable.to_string(), item);
if self.evaluate_predicate_from_row(condition, &iter_map) {
match_count += 1;
if kind == ListPredicateKind::Any {
return Value::Bool(true);
}
if kind == ListPredicateKind::None {
return Value::Bool(false);
}
if kind == ListPredicateKind::Single && match_count > 1 {
return Value::Bool(false);
}
} else if kind == ListPredicateKind::All {
return Value::Bool(false);
}
}
Value::Bool(match kind {
ListPredicateKind::All => true,
ListPredicateKind::Any => false,
ListPredicateKind::None => true,
ListPredicateKind::Single => match_count == 1,
})
}
fn evaluate_list_comprehension_from_path(
&self,
variable: &str,
list_expr: &Expression,
filter: &Option<Box<Expression>>,
transform: &Expression,
traverser: &crate::traversal::Traverser,
) -> Value {
let list_value = self.evaluate_value_from_path(list_expr, traverser);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let mut results = Vec::new();
for item in items {
let mut iter_map = std::collections::HashMap::new();
iter_map.insert(variable.to_string(), item);
if let Some(filter_expr) = filter {
if !self.evaluate_predicate_from_row(filter_expr, &iter_map) {
continue;
}
}
let transformed = self.evaluate_expression_from_row(transform, &iter_map);
results.push(transformed);
}
Value::List(results)
}
fn evaluate_function_call_from_path(
&self,
name: &str,
args: &[Expression],
traverser: &crate::traversal::Traverser,
) -> Value {
match name.to_uppercase().as_str() {
"COALESCE" => {
for arg in args {
let val = self.evaluate_value_from_path(arg, traverser);
if !matches!(val, Value::Null) {
return val;
}
}
Value::Null
}
"TOUPPER" | "UPPER" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value_from_path(arg, traverser) {
return Value::String(s.to_uppercase());
}
}
Value::Null
}
"TOLOWER" | "LOWER" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value_from_path(arg, traverser) {
return Value::String(s.to_lowercase());
}
}
Value::Null
}
"SIZE" | "LENGTH" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::String(s) => Value::Int(s.len() as i64),
Value::List(l) => Value::Int(l.len() as i64),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TRIM" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value_from_path(arg, traverser) {
return Value::String(s.trim().to_string());
}
}
Value::Null
}
"LTRIM" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value_from_path(arg, traverser) {
return Value::String(s.trim_start().to_string());
}
}
Value::Null
}
"RTRIM" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value_from_path(arg, traverser) {
return Value::String(s.trim_end().to_string());
}
}
Value::Null
}
"SUBSTRING" => {
if args.len() >= 2 {
let val = self.evaluate_value_from_path(&args[0], traverser);
let start = self.evaluate_value_from_path(&args[1], traverser);
let length = args
.get(2)
.map(|a| self.evaluate_value_from_path(a, traverser));
if let (Value::String(s), Value::Int(start_idx)) = (val, start) {
let start_idx = start_idx.max(0) as usize;
if start_idx >= s.len() {
return Value::String(String::new());
}
let result = if let Some(Value::Int(len)) = length {
let len = len.max(0) as usize;
s.chars().skip(start_idx).take(len).collect()
} else {
s.chars().skip(start_idx).collect()
};
return Value::String(result);
}
}
Value::Null
}
"REPLACE" => {
if args.len() >= 3 {
let val = self.evaluate_value_from_path(&args[0], traverser);
let search = self.evaluate_value_from_path(&args[1], traverser);
let replacement = self.evaluate_value_from_path(&args[2], traverser);
if let (Value::String(s), Value::String(search), Value::String(replacement)) =
(val, search, replacement)
{
return Value::String(s.replace(&search, &replacement));
}
}
Value::Null
}
"ABS" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Int(n.abs()),
Value::Float(f) => Value::Float(f.abs()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"CEIL" | "CEILING" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Float(f) => Value::Float(f.ceil()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"FLOOR" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Float(f) => Value::Float(f.floor()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ROUND" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Float(f) => Value::Float(f.round()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"SIGN" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Int(n.signum()),
Value::Float(f) => {
if f > 0.0 {
Value::Int(1)
} else if f < 0.0 {
Value::Int(-1)
} else {
Value::Int(0)
}
}
_ => Value::Null,
}
} else {
Value::Null
}
}
"SQRT" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) if n >= 0 => Value::Float((n as f64).sqrt()),
Value::Float(f) if f >= 0.0 => Value::Float(f.sqrt()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"POW" | "POWER" => {
if args.len() >= 2 {
let base = self.evaluate_value_from_path(&args[0], traverser);
let exp = self.evaluate_value_from_path(&args[1], traverser);
apply_binary_op(BinaryOperator::Pow, base, exp)
} else {
Value::Null
}
}
"LOG" | "LN" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) if n > 0 => Value::Float((n as f64).ln()),
Value::Float(f) if f > 0.0 => Value::Float(f.ln()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"LOG10" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) if n > 0 => Value::Float((n as f64).log10()),
Value::Float(f) if f > 0.0 => Value::Float(f.log10()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"EXP" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Float((n as f64).exp()),
Value::Float(f) => Value::Float(f.exp()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"SIN" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Float((n as f64).sin()),
Value::Float(f) => Value::Float(f.sin()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"COS" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Float((n as f64).cos()),
Value::Float(f) => Value::Float(f.cos()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TAN" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Float((n as f64).tan()),
Value::Float(f) => Value::Float(f.tan()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ASIN" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => {
let f = n as f64;
if (-1.0..=1.0).contains(&f) {
Value::Float(f.asin())
} else {
Value::Null
}
}
Value::Float(f) if (-1.0..=1.0).contains(&f) => Value::Float(f.asin()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ACOS" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => {
let f = n as f64;
if (-1.0..=1.0).contains(&f) {
Value::Float(f.acos())
} else {
Value::Null
}
}
Value::Float(f) if (-1.0..=1.0).contains(&f) => Value::Float(f.acos()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ATAN" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Float((n as f64).atan()),
Value::Float(f) => Value::Float(f.atan()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ATAN2" => {
if args.len() >= 2 {
let y = self.evaluate_value_from_path(&args[0], traverser);
let x = self.evaluate_value_from_path(&args[1], traverser);
match (y, x) {
(Value::Int(y), Value::Int(x)) => Value::Float((y as f64).atan2(x as f64)),
(Value::Float(y), Value::Float(x)) => Value::Float(y.atan2(x)),
(Value::Int(y), Value::Float(x)) => Value::Float((y as f64).atan2(x)),
(Value::Float(y), Value::Int(x)) => Value::Float(y.atan2(x as f64)),
_ => Value::Null,
}
} else {
Value::Null
}
}
"RADIANS" | "TORADIANS" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Float((n as f64).to_radians()),
Value::Float(f) => Value::Float(f.to_radians()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"DEGREES" | "TODEGREES" => {
if let Some(arg) = args.first() {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => Value::Float((n as f64).to_degrees()),
Value::Float(f) => Value::Float(f.to_degrees()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"PI" => Value::Float(std::f64::consts::PI),
"E" => Value::Float(std::f64::consts::E),
"MATH" => self.evaluate_math_from_path(args, traverser),
"TOSTRING" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value_from_path(arg, traverser);
match val {
Value::String(s) => Value::String(s),
Value::Int(n) => Value::String(n.to_string()),
Value::Float(f) => Value::String(f.to_string()),
Value::Bool(b) => Value::String(b.to_string()),
Value::Null => Value::Null,
_ => Value::Null,
}
} else {
Value::Null
}
}
"TOINTEGER" | "TOINT" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value_from_path(arg, traverser);
match val {
Value::Int(n) => Value::Int(n),
Value::Float(f) => Value::Int(f as i64),
Value::String(s) => {
s.parse::<i64>().ok().map(Value::Int).unwrap_or(Value::Null)
}
Value::Bool(b) => Value::Int(if b { 1 } else { 0 }),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TOFLOAT" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value_from_path(arg, traverser);
match val {
Value::Float(f) => Value::Float(f),
Value::Int(n) => Value::Float(n as f64),
Value::String(s) => s
.parse::<f64>()
.ok()
.map(Value::Float)
.unwrap_or(Value::Null),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TOBOOLEAN" | "TOBOOL" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value_from_path(arg, traverser);
match val {
Value::Bool(b) => Value::Bool(b),
Value::String(s) => match s.to_lowercase().as_str() {
"true" | "yes" | "1" => Value::Bool(true),
"false" | "no" | "0" => Value::Bool(false),
_ => Value::Null,
},
Value::Int(n) => Value::Bool(n != 0),
_ => Value::Null,
}
} else {
Value::Null
}
}
"PATH" => {
let path_values: Vec<Value> =
traverser.path.objects().map(|pv| pv.to_value()).collect();
Value::List(path_values)
}
"PROPERTIES" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value_from_path(arg, traverser);
match element_val {
Value::Vertex(vid) => {
if let Some(vertex) = self.snapshot.storage().get_vertex(vid) {
return Value::Map(vertex.properties.into_value_map());
}
}
Value::Edge(eid) => {
if let Some(edge) = self.snapshot.storage().get_edge(eid) {
return Value::Map(edge.properties.into_value_map());
}
}
_ => {}
}
}
Value::Null
}
"LABELS" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value_from_path(arg, traverser);
if let Value::Vertex(vid) = element_val {
if let Some(vertex) = self.snapshot.storage().get_vertex(vid) {
return Value::List(vec![Value::String(vertex.label)]);
}
}
}
Value::Null
}
"TYPE" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value_from_path(arg, traverser);
if let Value::Edge(eid) = element_val {
if let Some(edge) = self.snapshot.storage().get_edge(eid) {
return Value::String(edge.label);
}
}
}
Value::Null
}
"ID" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value_from_path(arg, traverser);
match element_val {
Value::Vertex(vid) => return id_to_value(vid.0),
Value::Edge(eid) => return id_to_value(eid.0),
_ => {}
}
}
Value::Null
}
_ => Value::Null,
}
}
fn evaluate_case_from_path(
&self,
case_expr: &CaseExpression,
traverser: &crate::traversal::Traverser,
) -> Value {
for (condition, result) in &case_expr.when_clauses {
if self.evaluate_predicate_from_path(condition, traverser) {
return self.evaluate_value_from_path(result, traverser);
}
}
if let Some(else_expr) = &case_expr.else_clause {
self.evaluate_value_from_path(else_expr, traverser)
} else {
Value::Null
}
}
fn get_variable_value_from_path(
&self,
variable: &str,
traverser: &crate::traversal::Traverser,
) -> Value {
if let Some(values) = traverser.path.get(variable) {
if let Some(path_value) = values.last() {
return path_value.to_value();
}
}
traverser.value.clone()
}
fn evaluate_return_for_traverser(
&self,
items: &[ReturnItem],
traverser: &crate::traversal::Traverser,
) -> Option<Value> {
if items.len() == 1 {
self.evaluate_expression_from_path(&items[0].expression, traverser)
} else {
let mut map = ValueMap::new();
for item in items {
let key = self.get_return_item_key(item);
let value = self.evaluate_expression_from_path(&item.expression, traverser)?;
map.insert(key, value);
}
Some(Value::Map(map))
}
}
fn evaluate_expression_from_path(
&self,
expr: &Expression,
traverser: &crate::traversal::Traverser,
) -> Option<Value> {
match expr {
Expression::Variable(var) => {
Some(self.get_variable_value_from_path(var, traverser))
}
Expression::Property { variable, property } => {
let element = self.get_variable_value_from_path(variable, traverser);
self.extract_property(&element, property)
}
Expression::Literal(lit) => Some(lit.clone().into()),
_ => {
Some(self.evaluate_value_from_path(expr, traverser))
}
}
}
fn deduplicate_results(&self, results: Vec<Value>) -> Vec<Value> {
let mut seen: Vec<ComparableValue> = Vec::new();
let mut deduped = Vec::new();
for value in results {
let comparable = ComparableValue::from(value.clone());
if !seen.contains(&comparable) {
seen.push(comparable);
deduped.push(value);
}
}
deduped
}
fn evaluate_return_for_element(&self, items: &[ReturnItem], element: &Value) -> Option<Value> {
if items.len() == 1 {
self.evaluate_expression(&items[0].expression, element)
} else {
let mut map = ValueMap::new();
for item in items {
let key = self.get_return_item_key(item);
let value = self.evaluate_expression(&item.expression, element)?;
map.insert(key, value);
}
Some(Value::Map(map))
}
}
fn get_return_item_key(&self, item: &ReturnItem) -> String {
if let Some(alias) = &item.alias {
alias.clone()
} else {
match &item.expression {
Expression::Variable(var) => var.clone(),
Expression::Property { variable, property } => {
format!("{}.{}", variable, property)
}
_ => "value".to_string(),
}
}
}
fn evaluate_expression(&self, expr: &Expression, element: &Value) -> Option<Value> {
match expr {
Expression::Variable(_) => {
Some(element.clone())
}
Expression::Property { property, .. } => {
self.extract_property(element, property)
}
Expression::Literal(lit) => {
Some(lit.clone().into())
}
_ => {
Some(self.evaluate_value(expr, element))
}
}
}
fn extract_property(&self, element: &Value, property: &str) -> Option<Value> {
match element {
Value::Vertex(id) => {
let vertex = self.snapshot.storage().get_vertex(*id)?;
vertex.properties.get(property).cloned()
}
Value::Edge(id) => {
let edge = self.snapshot.storage().get_edge(*id)?;
edge.properties.get(property).cloned()
}
Value::Map(map) => {
Some(map.get(property).cloned().unwrap_or(Value::Null))
}
Value::Null => Some(Value::Null), _ => None,
}
}
fn evaluate_predicate(&self, expr: &Expression, element: &Value) -> bool {
match expr {
Expression::BinaryOp { left, op, right } => {
match op {
BinaryOperator::And => {
self.evaluate_predicate(left, element)
&& self.evaluate_predicate(right, element)
}
BinaryOperator::Or => {
self.evaluate_predicate(left, element)
|| self.evaluate_predicate(right, element)
}
_ => {
let left_val = self.evaluate_value(left, element);
let right_val = self.evaluate_value(right, element);
apply_comparison(*op, &left_val, &right_val)
}
}
}
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => !self.evaluate_predicate(expr, element),
UnaryOperator::Neg => {
match self.evaluate_value(expr, element) {
Value::Int(n) => n == 0,
Value::Float(f) => f == 0.0,
Value::Bool(b) => !b,
Value::Null => true,
_ => false,
}
}
},
Expression::IsNull { expr, negated } => {
let val = self.evaluate_value(expr, element);
let is_null = matches!(val, Value::Null);
if *negated {
!is_null
} else {
is_null
}
}
Expression::InList {
expr,
list,
negated,
} => {
let val = self.evaluate_value(expr, element);
let in_list = list.iter().any(|item| {
let item_val = self.evaluate_value(item, element);
val == item_val
});
if *negated {
!in_list
} else {
in_list
}
}
Expression::Exists {
pattern,
negated,
where_expr,
} => {
let exists = self.evaluate_exists_pattern_with_where(
pattern,
where_expr.as_deref(),
element,
);
if *negated {
!exists
} else {
exists
}
}
_ => {
let val = self.evaluate_value(expr, element);
match val {
Value::Bool(b) => b,
Value::Null => false,
Value::Int(n) => n != 0,
Value::Float(f) => f != 0.0,
Value::String(s) => !s.is_empty(),
_ => true, }
}
}
}
fn evaluate_value(&self, expr: &Expression, element: &Value) -> Value {
match expr {
Expression::Literal(lit) => lit.clone().into(),
Expression::Variable(_) => {
element.clone()
}
Expression::Parameter(name) => {
self.parameters.get(name).cloned().unwrap_or(Value::Null)
}
Expression::Property { property, .. } => {
self.extract_property(element, property)
.unwrap_or(Value::Null)
}
Expression::BinaryOp { left, op, right } => {
let left_val = self.evaluate_value(left, element);
let right_val = self.evaluate_value(right, element);
apply_binary_op(*op, left_val, right_val)
}
Expression::UnaryOp { op, expr } => match op {
UnaryOperator::Not => {
let val = self.evaluate_value(expr, element);
match val {
Value::Bool(b) => Value::Bool(!b),
_ => Value::Null,
}
}
UnaryOperator::Neg => {
let val = self.evaluate_value(expr, element);
match val {
Value::Int(n) => Value::Int(-n),
Value::Float(f) => Value::Float(-f),
_ => Value::Null,
}
}
},
Expression::IsNull { expr, negated } => {
let val = self.evaluate_value(expr, element);
let is_null = matches!(val, Value::Null);
Value::Bool(if *negated { !is_null } else { is_null })
}
Expression::InList {
expr,
list,
negated,
} => {
let val = self.evaluate_value(expr, element);
let in_list = list.iter().any(|item| {
let item_val = self.evaluate_value(item, element);
val == item_val
});
Value::Bool(if *negated { !in_list } else { in_list })
}
Expression::List(items) => {
let values: Vec<Value> = items
.iter()
.map(|item| self.evaluate_value(item, element))
.collect();
Value::List(values)
}
Expression::Map(entries) => {
let map: ValueMap = entries
.iter()
.map(|(key, value_expr)| {
let value = self.evaluate_value(value_expr, element);
(key.clone(), value)
})
.collect();
Value::Map(map)
}
Expression::Exists {
pattern,
negated,
where_expr,
} => {
let exists = self.evaluate_exists_pattern_with_where(
pattern,
where_expr.as_deref(),
element,
);
Value::Bool(if *negated { !exists } else { exists })
}
Expression::FunctionCall { name, args } => {
self.evaluate_function_call(name, args, element)
}
Expression::Case(case_expr) => self.evaluate_case(case_expr, element),
Expression::Reduce {
accumulator,
initial,
variable,
list,
expression,
} => self.evaluate_reduce(accumulator, initial, variable, list, expression, element),
Expression::ListComprehension {
variable,
list,
filter,
transform,
} => self.evaluate_list_comprehension(variable, list, filter, transform, element),
Expression::All {
variable,
list,
condition,
} => self.evaluate_list_predicate(
ListPredicateKind::All,
variable,
list,
condition,
element,
),
Expression::Any {
variable,
list,
condition,
} => self.evaluate_list_predicate(
ListPredicateKind::Any,
variable,
list,
condition,
element,
),
Expression::None {
variable,
list,
condition,
} => self.evaluate_list_predicate(
ListPredicateKind::None,
variable,
list,
condition,
element,
),
Expression::Single {
variable,
list,
condition,
} => self.evaluate_list_predicate(
ListPredicateKind::Single,
variable,
list,
condition,
element,
),
Expression::Index { list, index } => self.evaluate_index(list, index, element),
Expression::Slice { list, start, end } => {
self.evaluate_slice(list, start.as_deref(), end.as_deref(), element)
}
Expression::PatternComprehension {
pattern,
filter,
transform,
} => {
if let Some(PatternElement::Node(node)) = pattern.elements.first() {
if let Some(var) = &node.variable {
let mut row = HashMap::new();
row.insert(var.clone(), element.clone());
self.evaluate_pattern_comprehension_from_row(
pattern,
filter.as_deref(),
transform,
&row,
)
} else {
Value::List(vec![])
}
} else {
Value::List(vec![])
}
}
_ => Value::Null, }
}
fn evaluate_reduce(
&self,
accumulator: &str,
initial: &Expression,
variable: &str,
list_expr: &Expression,
expression: &Expression,
element: &Value,
) -> Value {
let mut acc_value = self.evaluate_value(initial, element);
let list_value = self.evaluate_value(list_expr, element);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
for item in items {
let mut iter_map = std::collections::HashMap::new();
iter_map.insert(accumulator.to_string(), acc_value);
iter_map.insert(variable.to_string(), item);
acc_value = self.evaluate_expression_from_row(expression, &iter_map);
}
acc_value
}
fn evaluate_list_predicate(
&self,
kind: ListPredicateKind,
variable: &str,
list_expr: &Expression,
condition: &Expression,
element: &Value,
) -> Value {
let list_value = self.evaluate_value(list_expr, element);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let mut match_count = 0;
for item in items {
let mut iter_map = std::collections::HashMap::new();
iter_map.insert(variable.to_string(), item);
for bound_var in self.bindings.keys() {
iter_map.insert(bound_var.clone(), element.clone());
}
if self.evaluate_predicate_from_row(condition, &iter_map) {
match_count += 1;
if kind == ListPredicateKind::Any {
return Value::Bool(true);
}
if kind == ListPredicateKind::None {
return Value::Bool(false);
}
if kind == ListPredicateKind::Single && match_count > 1 {
return Value::Bool(false);
}
} else if kind == ListPredicateKind::All {
return Value::Bool(false);
}
}
Value::Bool(match kind {
ListPredicateKind::All => true,
ListPredicateKind::Any => false,
ListPredicateKind::None => true,
ListPredicateKind::Single => match_count == 1,
})
}
fn evaluate_list_comprehension(
&self,
variable: &str,
list_expr: &Expression,
filter: &Option<Box<Expression>>,
transform: &Expression,
element: &Value,
) -> Value {
let list_value = self.evaluate_value(list_expr, element);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let mut results = Vec::new();
for item in items {
let mut iter_map = std::collections::HashMap::new();
iter_map.insert(variable.to_string(), item);
if let Some(filter_expr) = filter {
if !self.evaluate_predicate_from_row(filter_expr, &iter_map) {
continue;
}
}
let transformed = self.evaluate_expression_from_row(transform, &iter_map);
results.push(transformed);
}
Value::List(results)
}
fn evaluate_index(
&self,
list_expr: &Expression,
index_expr: &Expression,
element: &Value,
) -> Value {
let list_value = self.evaluate_value(list_expr, element);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let index_value = self.evaluate_value(index_expr, element);
let index = match index_value {
Value::Int(i) => i,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let len = items.len() as i64;
let resolved_index = if index < 0 {
len + index } else {
index
};
if resolved_index < 0 || resolved_index >= len {
return Value::Null;
}
items
.into_iter()
.nth(resolved_index as usize)
.unwrap_or(Value::Null)
}
fn evaluate_slice(
&self,
list_expr: &Expression,
start_expr: Option<&Expression>,
end_expr: Option<&Expression>,
element: &Value,
) -> Value {
let list_value = self.evaluate_value(list_expr, element);
let items = match list_value {
Value::List(items) => items,
Value::Null => return Value::Null,
_ => return Value::Null,
};
let len = items.len() as i64;
let start = if let Some(expr) = start_expr {
match self.evaluate_value(expr, element) {
Value::Int(i) => i,
Value::Null => return Value::Null,
_ => return Value::Null,
}
} else {
0
};
let end = if let Some(expr) = end_expr {
match self.evaluate_value(expr, element) {
Value::Int(i) => i,
Value::Null => return Value::Null,
_ => return Value::Null,
}
} else {
len
};
let resolved_start = if start < 0 { len + start } else { start };
let resolved_end = if end < 0 { len + end } else { end };
let clamped_start = resolved_start.clamp(0, len) as usize;
let clamped_end = resolved_end.clamp(0, len) as usize;
if clamped_start >= clamped_end {
return Value::List(vec![]);
}
let slice: Vec<Value> = items
.into_iter()
.skip(clamped_start)
.take(clamped_end - clamped_start)
.collect();
Value::List(slice)
}
fn evaluate_function_call(&self, name: &str, args: &[Expression], element: &Value) -> Value {
match name.to_uppercase().as_str() {
"COALESCE" => {
for arg in args {
let val = self.evaluate_value(arg, element);
if !matches!(val, Value::Null) {
return val;
}
}
Value::Null
}
"TOUPPER" | "UPPER" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value(arg, element) {
return Value::String(s.to_uppercase());
}
}
Value::Null
}
"TOLOWER" | "LOWER" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value(arg, element) {
return Value::String(s.to_lowercase());
}
}
Value::Null
}
"SIZE" | "LENGTH" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::String(s) => Value::Int(s.len() as i64),
Value::List(l) => Value::Int(l.len() as i64),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TRIM" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value(arg, element) {
return Value::String(s.trim().to_string());
}
}
Value::Null
}
"LTRIM" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value(arg, element) {
return Value::String(s.trim_start().to_string());
}
}
Value::Null
}
"RTRIM" => {
if let Some(arg) = args.first() {
if let Value::String(s) = self.evaluate_value(arg, element) {
return Value::String(s.trim_end().to_string());
}
}
Value::Null
}
"SUBSTRING" => {
if args.len() >= 2 {
let val = self.evaluate_value(&args[0], element);
let start = self.evaluate_value(&args[1], element);
let length = args.get(2).map(|a| self.evaluate_value(a, element));
if let (Value::String(s), Value::Int(start_idx)) = (val, start) {
let start_idx = start_idx.max(0) as usize;
if start_idx >= s.len() {
return Value::String(String::new());
}
let result = if let Some(Value::Int(len)) = length {
let len = len.max(0) as usize;
s.chars().skip(start_idx).take(len).collect()
} else {
s.chars().skip(start_idx).collect()
};
return Value::String(result);
}
}
Value::Null
}
"REPLACE" => {
if args.len() >= 3 {
let val = self.evaluate_value(&args[0], element);
let search = self.evaluate_value(&args[1], element);
let replacement = self.evaluate_value(&args[2], element);
if let (Value::String(s), Value::String(search), Value::String(replacement)) =
(val, search, replacement)
{
return Value::String(s.replace(&search, &replacement));
}
}
Value::Null
}
"ABS" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Int(n.abs()),
Value::Float(f) => Value::Float(f.abs()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"CEIL" | "CEILING" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Float(f) => Value::Float(f.ceil()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"FLOOR" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Float(f) => Value::Float(f.floor()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ROUND" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Float(f) => Value::Float(f.round()),
Value::Int(n) => Value::Int(n),
_ => Value::Null,
}
} else {
Value::Null
}
}
"SIGN" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Int(n.signum()),
Value::Float(f) => {
if f > 0.0 {
Value::Int(1)
} else if f < 0.0 {
Value::Int(-1)
} else {
Value::Int(0)
}
}
_ => Value::Null,
}
} else {
Value::Null
}
}
"SQRT" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) if n >= 0 => Value::Float((n as f64).sqrt()),
Value::Float(f) if f >= 0.0 => Value::Float(f.sqrt()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"POW" | "POWER" => {
if args.len() >= 2 {
let base = self.evaluate_value(&args[0], element);
let exp = self.evaluate_value(&args[1], element);
apply_binary_op(BinaryOperator::Pow, base, exp)
} else {
Value::Null
}
}
"LOG" | "LN" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) if n > 0 => Value::Float((n as f64).ln()),
Value::Float(f) if f > 0.0 => Value::Float(f.ln()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"LOG10" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) if n > 0 => Value::Float((n as f64).log10()),
Value::Float(f) if f > 0.0 => Value::Float(f.log10()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"EXP" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Float((n as f64).exp()),
Value::Float(f) => Value::Float(f.exp()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"SIN" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Float((n as f64).sin()),
Value::Float(f) => Value::Float(f.sin()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"COS" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Float((n as f64).cos()),
Value::Float(f) => Value::Float(f.cos()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TAN" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Float((n as f64).tan()),
Value::Float(f) => Value::Float(f.tan()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ASIN" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => {
let f = n as f64;
if (-1.0..=1.0).contains(&f) {
Value::Float(f.asin())
} else {
Value::Null
}
}
Value::Float(f) if (-1.0..=1.0).contains(&f) => Value::Float(f.asin()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ACOS" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => {
let f = n as f64;
if (-1.0..=1.0).contains(&f) {
Value::Float(f.acos())
} else {
Value::Null
}
}
Value::Float(f) if (-1.0..=1.0).contains(&f) => Value::Float(f.acos()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ATAN" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Float((n as f64).atan()),
Value::Float(f) => Value::Float(f.atan()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"ATAN2" => {
if args.len() >= 2 {
let y = self.evaluate_value(&args[0], element);
let x = self.evaluate_value(&args[1], element);
match (y, x) {
(Value::Int(y), Value::Int(x)) => Value::Float((y as f64).atan2(x as f64)),
(Value::Float(y), Value::Float(x)) => Value::Float(y.atan2(x)),
(Value::Int(y), Value::Float(x)) => Value::Float((y as f64).atan2(x)),
(Value::Float(y), Value::Int(x)) => Value::Float(y.atan2(x as f64)),
_ => Value::Null,
}
} else {
Value::Null
}
}
"RADIANS" | "TORADIANS" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Float((n as f64).to_radians()),
Value::Float(f) => Value::Float(f.to_radians()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"DEGREES" | "TODEGREES" => {
if let Some(arg) = args.first() {
match self.evaluate_value(arg, element) {
Value::Int(n) => Value::Float((n as f64).to_degrees()),
Value::Float(f) => Value::Float(f.to_degrees()),
_ => Value::Null,
}
} else {
Value::Null
}
}
"PI" => Value::Float(std::f64::consts::PI),
"E" => Value::Float(std::f64::consts::E),
"MATH" => self.evaluate_math(args, element),
"TOSTRING" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value(arg, element);
match val {
Value::String(s) => Value::String(s),
Value::Int(n) => Value::String(n.to_string()),
Value::Float(f) => Value::String(f.to_string()),
Value::Bool(b) => Value::String(b.to_string()),
Value::Null => Value::Null,
_ => Value::Null,
}
} else {
Value::Null
}
}
"TOINTEGER" | "TOINT" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value(arg, element);
match val {
Value::Int(n) => Value::Int(n),
Value::Float(f) => Value::Int(f as i64),
Value::String(s) => {
s.parse::<i64>().ok().map(Value::Int).unwrap_or(Value::Null)
}
Value::Bool(b) => Value::Int(if b { 1 } else { 0 }),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TOFLOAT" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value(arg, element);
match val {
Value::Float(f) => Value::Float(f),
Value::Int(n) => Value::Float(n as f64),
Value::String(s) => s
.parse::<f64>()
.ok()
.map(Value::Float)
.unwrap_or(Value::Null),
_ => Value::Null,
}
} else {
Value::Null
}
}
"TOBOOLEAN" | "TOBOOL" => {
if let Some(arg) = args.first() {
let val = self.evaluate_value(arg, element);
match val {
Value::Bool(b) => Value::Bool(b),
Value::String(s) => match s.to_lowercase().as_str() {
"true" | "yes" | "1" => Value::Bool(true),
"false" | "no" | "0" => Value::Bool(false),
_ => Value::Null,
},
Value::Int(n) => Value::Bool(n != 0),
_ => Value::Null,
}
} else {
Value::Null
}
}
"PROPERTIES" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value(arg, element);
match element_val {
Value::Vertex(vid) => {
if let Some(vertex) = self.snapshot.storage().get_vertex(vid) {
return Value::Map(vertex.properties.into_value_map());
}
}
Value::Edge(eid) => {
if let Some(edge) = self.snapshot.storage().get_edge(eid) {
return Value::Map(edge.properties.into_value_map());
}
}
_ => {}
}
}
Value::Null
}
"LABELS" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value(arg, element);
if let Value::Vertex(vid) = element_val {
if let Some(vertex) = self.snapshot.storage().get_vertex(vid) {
return Value::List(vec![Value::String(vertex.label)]);
}
}
}
Value::Null
}
"TYPE" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value(arg, element);
if let Value::Edge(eid) = element_val {
if let Some(edge) = self.snapshot.storage().get_edge(eid) {
return Value::String(edge.label);
}
}
}
Value::Null
}
"ID" => {
if let Some(arg) = args.first() {
let element_val = self.evaluate_value(arg, element);
match element_val {
Value::Vertex(vid) => return id_to_value(vid.0),
Value::Edge(eid) => return id_to_value(eid.0),
_ => {}
}
}
Value::Null
}
_ => Value::Null,
}
}
fn evaluate_case(&self, case_expr: &CaseExpression, element: &Value) -> Value {
for (condition, result) in &case_expr.when_clauses {
if self.evaluate_predicate(condition, element) {
return self.evaluate_value(result, element);
}
}
if let Some(else_expr) = &case_expr.else_clause {
self.evaluate_value(else_expr, element)
} else {
Value::Null
}
}
fn evaluate_exists_pattern_with_where(
&self,
pattern: &Pattern,
where_expr: Option<&Expression>,
element: &Value,
) -> bool {
let vid = match element {
Value::Vertex(id) => *id,
_ => return false,
};
let g = crate::traversal::GraphTraversalSource::from_snapshot(self.snapshot);
let mut traversal = g.v_ids([vid]);
if where_expr.is_some() {
traversal = traversal.with_path();
}
for elem in &pattern.elements {
match elem {
PatternElement::Node(node) => {
traversal = self.apply_node_filters(node, traversal);
if where_expr.is_some() {
if let Some(var) = &node.variable {
traversal = traversal.as_(var.as_str());
}
}
}
PatternElement::Edge(edge) => {
traversal = self.apply_edge_navigation(edge, traversal);
if where_expr.is_some() {
if let Some(var) = &edge.variable {
traversal = traversal.as_(var.as_str());
}
}
}
}
}
let Some(predicate) = where_expr else {
return !traversal.to_list().is_empty();
};
for traverser in traversal.traversers() {
if self.evaluate_predicate_from_path(predicate, &traverser) {
return true;
}
}
false
}
fn apply_node_filters(
&self,
node: &NodePattern,
mut traversal: BoundTraversal<'a, (), Value>,
) -> BoundTraversal<'a, (), Value> {
if !node.labels.is_empty() {
let labels: Vec<&str> = node.labels.iter().map(|s| s.as_str()).collect();
traversal = traversal.has_label_any(labels);
}
for (key, value) in &node.properties {
let val: Value = value.clone().into();
traversal = traversal.has_value(key.as_str(), val);
}
traversal
}
fn apply_edge_navigation(
&self,
edge: &EdgePattern,
traversal: BoundTraversal<'a, (), Value>,
) -> BoundTraversal<'a, (), Value> {
let labels: Vec<&str> = edge.labels.iter().map(|s| s.as_str()).collect();
match edge.direction {
EdgeDirection::Outgoing => {
if labels.is_empty() {
traversal.out()
} else {
traversal.out_labels(&labels)
}
}
EdgeDirection::Incoming => {
if labels.is_empty() {
traversal.in_()
} else {
traversal.in_labels(&labels)
}
}
EdgeDirection::Both => {
if labels.is_empty() {
traversal.both()
} else {
traversal.both_labels(&labels)
}
}
}
}
fn validate_expression_variables(&self, expr: &Expression) -> Result<(), CompileError> {
match expr {
Expression::Variable(var) => {
if !self.bindings.contains_key(var) && var != "*" {
return Err(CompileError::undefined_variable(var));
}
}
Expression::Property { variable, .. } => {
if !self.bindings.contains_key(variable) {
return Err(CompileError::undefined_variable(variable));
}
}
Expression::BinaryOp { left, right, .. } => {
self.validate_expression_variables(left)?;
self.validate_expression_variables(right)?;
}
Expression::UnaryOp { expr, .. } => {
self.validate_expression_variables(expr)?;
}
Expression::IsNull { expr, .. } => {
self.validate_expression_variables(expr)?;
}
Expression::InList { expr, list, .. } => {
self.validate_expression_variables(expr)?;
for item in list {
self.validate_expression_variables(item)?;
}
}
Expression::List(items) => {
for item in items {
self.validate_expression_variables(item)?;
}
}
Expression::Map(entries) => {
for (_, value) in entries {
self.validate_expression_variables(value)?;
}
}
Expression::FunctionCall { args, .. } => {
for arg in args {
self.validate_expression_variables(arg)?;
}
}
Expression::Aggregate { expr, .. } => {
self.validate_expression_variables(expr)?;
}
Expression::Exists { pattern, .. } => {
if let Some(PatternElement::Node(node)) = pattern.elements.first() {
if let Some(var) = &node.variable {
if !self.bindings.contains_key(var) {
return Err(CompileError::undefined_variable(var));
}
}
}
}
Expression::Case(case_expr) => {
for (condition, result) in &case_expr.when_clauses {
self.validate_expression_variables(condition)?;
self.validate_expression_variables(result)?;
}
if let Some(else_expr) = &case_expr.else_clause {
self.validate_expression_variables(else_expr)?;
}
}
Expression::Literal(_) => {
}
Expression::Parameter(name) => {
if !self.parameters.contains_key(name) {
return Err(CompileError::unbound_parameter(name));
}
}
Expression::ListComprehension {
variable,
list,
filter,
transform,
} => {
self.validate_expression_variables(list)?;
let _ = variable; let _ = filter;
let _ = transform;
}
Expression::Reduce {
accumulator,
initial,
variable,
list,
expression,
} => {
self.validate_expression_variables(initial)?;
self.validate_expression_variables(list)?;
let _ = accumulator;
let _ = variable;
let _ = expression;
}
Expression::All {
variable,
list,
condition,
}
| Expression::Any {
variable,
list,
condition,
}
| Expression::None {
variable,
list,
condition,
}
| Expression::Single {
variable,
list,
condition,
} => {
self.validate_expression_variables(list)?;
let _ = variable;
let _ = condition;
}
Expression::Index { list, index } => {
self.validate_expression_variables(list)?;
self.validate_expression_variables(index)?;
}
Expression::Slice { list, start, end } => {
self.validate_expression_variables(list)?;
if let Some(s) = start {
self.validate_expression_variables(s)?;
}
if let Some(e) = end {
self.validate_expression_variables(e)?;
}
}
Expression::PatternComprehension {
pattern,
filter,
transform,
} => {
if let Some(PatternElement::Node(node)) = pattern.elements.first() {
if let Some(var) = &node.variable {
if !self.bindings.contains_key(var) {
return Err(CompileError::undefined_variable(var));
}
}
}
let _ = filter;
let _ = transform;
}
Expression::GeoPoint { .. }
| Expression::GeoPolygon(_)
| Expression::GeoDistance { .. } => {
}
Expression::GeoDistanceFn { a, b } => {
self.validate_expression_variables(a)?;
self.validate_expression_variables(b)?;
}
Expression::GeoWithinBBox {
point,
min_lon,
min_lat,
max_lon,
max_lat,
} => {
self.validate_expression_variables(point)?;
self.validate_expression_variables(min_lon)?;
self.validate_expression_variables(min_lat)?;
self.validate_expression_variables(max_lon)?;
self.validate_expression_variables(max_lat)?;
}
Expression::GeoWithinDistanceFn { a, b, distance } => {
self.validate_expression_variables(a)?;
self.validate_expression_variables(b)?;
self.validate_expression_variables(distance)?;
}
}
Ok(())
}
fn has_aggregates(&self, return_clause: &ReturnClause) -> bool {
return_clause
.items
.iter()
.any(|item| self.expression_has_aggregate(&item.expression))
}
fn expression_has_aggregate(&self, expr: &Expression) -> bool {
Self::expr_has_aggregate(expr)
}
fn expr_has_aggregate(expr: &Expression) -> bool {
match expr {
Expression::Aggregate { .. } => true,
Expression::BinaryOp { left, right, .. } => {
Self::expr_has_aggregate(left) || Self::expr_has_aggregate(right)
}
Expression::UnaryOp { expr, .. } => Self::expr_has_aggregate(expr),
Expression::IsNull { expr, .. } => Self::expr_has_aggregate(expr),
Expression::InList { expr, list, .. } => {
Self::expr_has_aggregate(expr) || list.iter().any(Self::expr_has_aggregate)
}
Expression::List(items) => items.iter().any(Self::expr_has_aggregate),
Expression::Map(entries) => entries.iter().any(|(_, v)| Self::expr_has_aggregate(v)),
Expression::FunctionCall { args, .. } => args.iter().any(Self::expr_has_aggregate),
Expression::Case(case_expr) => {
case_expr
.when_clauses
.iter()
.any(|(c, r)| Self::expr_has_aggregate(c) || Self::expr_has_aggregate(r))
|| case_expr
.else_clause
.as_ref()
.map(|e| Self::expr_has_aggregate(e))
.unwrap_or(false)
}
Expression::Index { list, index } => {
Self::expr_has_aggregate(list) || Self::expr_has_aggregate(index)
}
Expression::Slice { list, start, end } => {
Self::expr_has_aggregate(list)
|| start
.as_ref()
.map(|s| Self::expr_has_aggregate(s))
.unwrap_or(false)
|| end
.as_ref()
.map(|e| Self::expr_has_aggregate(e))
.unwrap_or(false)
}
_ => false,
}
}
fn execute_aggregated_return(
&self,
return_clause: &ReturnClause,
where_clause: &Option<WhereClause>,
having_clause: &Option<HavingClause>,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
let matched_elements: Vec<Value> = traversal.to_list();
let filtered_elements: Vec<Value> = if let Some(where_cl) = where_clause {
matched_elements
.into_iter()
.filter(|element| self.evaluate_predicate(&where_cl.expression, element))
.collect()
} else {
matched_elements
};
let mut group_by_items: Vec<(&ReturnItem, &Expression)> = Vec::new();
let mut aggregate_items: Vec<(&ReturnItem, AggregateFunc, bool, &Expression)> = Vec::new();
for item in &return_clause.items {
if let Expression::Aggregate {
func,
distinct,
expr,
} = &item.expression
{
aggregate_items.push((item, *func, *distinct, expr.as_ref()));
} else {
group_by_items.push((item, &item.expression));
}
}
let results = if group_by_items.is_empty() {
self.execute_global_aggregates(
return_clause,
&aggregate_items,
&filtered_elements,
having_clause,
)?
} else {
self.execute_grouped_aggregates(
return_clause,
&group_by_items,
&aggregate_items,
&filtered_elements,
having_clause,
)?
};
Ok(results)
}
fn execute_group_by_query(
&self,
return_clause: &ReturnClause,
where_clause: &Option<WhereClause>,
group_by: &GroupByClause,
having_clause: &Option<HavingClause>,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
if self.has_multi_vars {
return self.execute_group_by_query_multi_var(
return_clause,
where_clause,
group_by,
having_clause,
traversal,
);
}
let matched_elements: Vec<Value> = traversal.to_list();
let filtered_elements: Vec<Value> = if let Some(where_cl) = where_clause {
matched_elements
.into_iter()
.filter(|element| self.evaluate_predicate(&where_cl.expression, element))
.collect()
} else {
matched_elements
};
for item in &return_clause.items {
if !Self::expr_has_aggregate(&item.expression) {
if !self.expression_in_group_by(&item.expression, group_by) {
let expr_str = self.expression_to_string(&item.expression);
return Err(CompileError::expression_not_in_group_by(expr_str));
}
}
}
let mut groups: HashMap<Vec<ComparableValue>, Vec<Value>> = HashMap::new();
for element in filtered_elements {
let group_key: Vec<ComparableValue> = group_by
.expressions
.iter()
.map(|expr| {
let val = self.evaluate_value(expr, &element);
ComparableValue::from(val)
})
.collect();
groups.entry(group_key).or_default().push(element);
}
let mut results = Vec::new();
for (group_key, group_elements) in groups {
let result =
self.compute_group_result(return_clause, group_by, &group_key, &group_elements)?;
if let Some(having) = having_clause {
if !self.evaluate_having_predicate(
&having.expression,
return_clause,
group_by,
&group_key,
&group_elements,
&result,
) {
continue; }
}
results.push(result);
}
let results = if return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
Ok(results)
}
fn execute_group_by_query_multi_var(
&self,
return_clause: &ReturnClause,
where_clause: &Option<WhereClause>,
group_by: &GroupByClause,
having_clause: &Option<HavingClause>,
traversal: BoundTraversal<'a, (), Value>,
) -> Result<Vec<Value>, CompileError> {
use crate::traversal::Traverser;
let traversers: Vec<Traverser> = traversal.execute().collect();
let filtered_traversers: Vec<Traverser> = if let Some(where_cl) = where_clause {
traversers
.into_iter()
.filter(|t| self.evaluate_predicate_from_path(&where_cl.expression, t))
.collect()
} else {
traversers
};
for item in &return_clause.items {
if !Self::expr_has_aggregate(&item.expression)
&& !self.expression_in_group_by(&item.expression, group_by)
{
let expr_str = self.expression_to_string(&item.expression);
return Err(CompileError::expression_not_in_group_by(expr_str));
}
}
let mut groups: HashMap<Vec<ComparableValue>, Vec<Traverser>> = HashMap::new();
for t in filtered_traversers {
let group_key: Vec<ComparableValue> = group_by
.expressions
.iter()
.map(|expr| {
let val = self.evaluate_value_from_path(expr, &t);
ComparableValue::from(val)
})
.collect();
groups.entry(group_key).or_default().push(t);
}
let mut results = Vec::new();
for (group_key, group_traversers) in groups {
let result = self.compute_group_result_multi_var(
return_clause,
group_by,
&group_key,
&group_traversers,
)?;
if let Some(having) = having_clause {
if !self.evaluate_having_predicate_multi_var(
&having.expression,
return_clause,
group_by,
&group_key,
&group_traversers,
&result,
) {
continue; }
}
results.push(result);
}
let results = if return_clause.distinct {
self.deduplicate_results(results)
} else {
results
};
Ok(results)
}
fn compute_group_result_multi_var(
&self,
return_clause: &ReturnClause,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_traversers: &[crate::traversal::Traverser],
) -> Result<Value, CompileError> {
if return_clause.items.len() == 1 {
let item = &return_clause.items[0];
let value = self.evaluate_group_expression_multi_var(
&item.expression,
group_by,
group_key,
group_traversers,
)?;
if item.alias.is_some() {
let mut map = ValueMap::new();
map.insert(self.get_return_item_key(item), value);
Ok(Value::Map(map))
} else {
Ok(value)
}
} else {
let mut map = ValueMap::new();
for item in &return_clause.items {
let key = self.get_return_item_key(item);
let value = self.evaluate_group_expression_multi_var(
&item.expression,
group_by,
group_key,
group_traversers,
)?;
map.insert(key, value);
}
Ok(Value::Map(map))
}
}
fn evaluate_group_expression_multi_var(
&self,
expr: &Expression,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_traversers: &[crate::traversal::Traverser],
) -> Result<Value, CompileError> {
if let Expression::Aggregate {
func,
distinct,
expr: inner,
} = expr
{
self.compute_aggregate_multi_var(*func, *distinct, inner, group_traversers)
} else {
for (i, group_expr) in group_by.expressions.iter().enumerate() {
if self.expressions_match(expr, group_expr) {
return Ok(group_key[i].clone().into());
}
}
group_traversers
.first()
.map(|t| self.evaluate_value_from_path(expr, t))
.ok_or(CompileError::EmptyPattern)
}
}
fn compute_aggregate_multi_var(
&self,
func: AggregateFunc,
distinct: bool,
expr: &Expression,
traversers: &[crate::traversal::Traverser],
) -> Result<Value, CompileError> {
let is_count_star = matches!(func, AggregateFunc::Count)
&& matches!(expr, Expression::Variable(v) if v == "*");
if is_count_star {
return Ok(Value::Int(traversers.len() as i64));
}
let mut values: Vec<Value> = traversers
.iter()
.filter_map(|t| {
let val = self.evaluate_value_from_path(expr, t);
if matches!(val, Value::Null) {
None
} else {
Some(val)
}
})
.collect();
if distinct {
let mut seen: Vec<ComparableValue> = Vec::new();
values.retain(|v| {
let comparable = ComparableValue::from(v.clone());
if seen.contains(&comparable) {
false
} else {
seen.push(comparable);
true
}
});
}
match func {
AggregateFunc::Count => Ok(Value::Int(values.len() as i64)),
AggregateFunc::Sum => {
let mut int_sum: i64 = 0;
let mut float_sum: f64 = 0.0;
let mut has_float = false;
for v in &values {
match v {
Value::Int(n) => int_sum += n,
Value::Float(f) => {
has_float = true;
float_sum += f;
}
_ => {}
}
}
if has_float {
Ok(Value::Float(int_sum as f64 + float_sum))
} else {
Ok(Value::Int(int_sum))
}
}
AggregateFunc::Avg => {
let mut sum: f64 = 0.0;
let mut count: usize = 0;
for v in &values {
match v {
Value::Int(n) => {
sum += *n as f64;
count += 1;
}
Value::Float(f) => {
sum += f;
count += 1;
}
_ => {}
}
}
if count > 0 {
Ok(Value::Float(sum / count as f64))
} else {
Ok(Value::Null)
}
}
AggregateFunc::Min => values
.into_iter()
.filter(|v| !matches!(v, Value::Null))
.min_by(compare_values)
.map(Ok)
.unwrap_or(Ok(Value::Null)),
AggregateFunc::Max => values
.into_iter()
.filter(|v| !matches!(v, Value::Null))
.max_by(compare_values)
.map(Ok)
.unwrap_or(Ok(Value::Null)),
AggregateFunc::Collect => Ok(Value::List(values)),
}
}
fn expression_in_group_by(&self, expr: &Expression, group_by: &GroupByClause) -> bool {
group_by
.expressions
.iter()
.any(|group_expr| self.expressions_match(expr, group_expr))
}
fn expressions_match(&self, a: &Expression, b: &Expression) -> bool {
match (a, b) {
(Expression::Variable(va), Expression::Variable(vb)) => va == vb,
(
Expression::Property {
variable: va,
property: pa,
},
Expression::Property {
variable: vb,
property: pb,
},
) => va == vb && pa == pb,
(Expression::Literal(la), Expression::Literal(lb)) => la == lb,
_ => false,
}
}
fn expression_to_string(&self, expr: &Expression) -> String {
match expr {
Expression::Variable(v) => v.clone(),
Expression::Property { variable, property } => format!("{}.{}", variable, property),
Expression::Literal(lit) => match lit {
Literal::Null => "null".to_string(),
Literal::Bool(b) => b.to_string(),
Literal::Int(n) => n.to_string(),
Literal::Float(f) => f.to_string(),
Literal::String(s) => format!("'{}'", s),
},
Expression::Aggregate { func, .. } => {
let func_name = match func {
AggregateFunc::Count => "count",
AggregateFunc::Sum => "sum",
AggregateFunc::Avg => "avg",
AggregateFunc::Min => "min",
AggregateFunc::Max => "max",
AggregateFunc::Collect => "collect",
};
format!("{}(...)", func_name)
}
_ => "<expression>".to_string(),
}
}
fn compute_group_result(
&self,
return_clause: &ReturnClause,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_elements: &[Value],
) -> Result<Value, CompileError> {
if return_clause.items.len() == 1 {
let item = &return_clause.items[0];
let value = self.evaluate_group_expression(
&item.expression,
group_by,
group_key,
group_elements,
)?;
if item.alias.is_some() {
let mut map = ValueMap::new();
map.insert(self.get_return_item_key(item), value);
Ok(Value::Map(map))
} else {
Ok(value)
}
} else {
let mut map = ValueMap::new();
for item in &return_clause.items {
let key = self.get_return_item_key(item);
let value = self.evaluate_group_expression(
&item.expression,
group_by,
group_key,
group_elements,
)?;
map.insert(key, value);
}
Ok(Value::Map(map))
}
}
fn evaluate_group_expression(
&self,
expr: &Expression,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_elements: &[Value],
) -> Result<Value, CompileError> {
if let Expression::Aggregate {
func,
distinct,
expr: inner,
} = expr
{
self.compute_aggregate(*func, *distinct, inner, group_elements)
} else {
for (i, group_expr) in group_by.expressions.iter().enumerate() {
if self.expressions_match(expr, group_expr) {
return Ok(group_key[i].clone().into());
}
}
group_elements
.first()
.map(|e| self.evaluate_value(expr, e))
.ok_or(CompileError::EmptyPattern)
}
}
fn evaluate_having_predicate(
&self,
expr: &Expression,
return_clause: &ReturnClause,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_elements: &[Value],
result_map: &Value,
) -> bool {
match expr {
Expression::BinaryOp { left, op, right } => {
match op {
BinaryOperator::And => {
self.evaluate_having_predicate(
left,
return_clause,
group_by,
group_key,
group_elements,
result_map,
) && self.evaluate_having_predicate(
right,
return_clause,
group_by,
group_key,
group_elements,
result_map,
)
}
BinaryOperator::Or => {
self.evaluate_having_predicate(
left,
return_clause,
group_by,
group_key,
group_elements,
result_map,
) || self.evaluate_having_predicate(
right,
return_clause,
group_by,
group_key,
group_elements,
result_map,
)
}
_ => {
let left_val = self.evaluate_having_value(
left,
return_clause,
group_by,
group_key,
group_elements,
result_map,
);
let right_val = self.evaluate_having_value(
right,
return_clause,
group_by,
group_key,
group_elements,
result_map,
);
apply_comparison(*op, &left_val, &right_val)
}
}
}
Expression::UnaryOp { op, expr: inner } => match op {
UnaryOperator::Not => !self.evaluate_having_predicate(
inner,
return_clause,
group_by,
group_key,
group_elements,
result_map,
),
UnaryOperator::Neg => {
let val = self.evaluate_having_value(
inner,
return_clause,
group_by,
group_key,
group_elements,
result_map,
);
match val {
Value::Int(n) => n == 0,
Value::Float(f) => f == 0.0,
Value::Bool(b) => !b,
Value::Null => true,
_ => false,
}
}
},
_ => {
let val = self.evaluate_having_value(
expr,
return_clause,
group_by,
group_key,
group_elements,
result_map,
);
match val {
Value::Bool(b) => b,
Value::Null => false,
Value::Int(n) => n != 0,
Value::Float(f) => f != 0.0,
Value::String(s) => !s.is_empty(),
_ => true,
}
}
}
}
#[allow(clippy::only_used_in_recursion)]
fn evaluate_having_value(
&self,
expr: &Expression,
return_clause: &ReturnClause,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_elements: &[Value],
result_map: &Value,
) -> Value {
match expr {
Expression::Literal(lit) => lit.clone().into(),
Expression::Variable(name) => {
if let Value::Map(map) = result_map {
if let Some(val) = map.get(name) {
return val.clone();
}
}
group_elements.first().cloned().unwrap_or(Value::Null)
}
Expression::Aggregate {
func,
distinct,
expr: inner,
} => {
self.compute_aggregate(*func, *distinct, inner, group_elements)
.unwrap_or(Value::Null)
}
Expression::Property { variable, property } => {
if let Value::Map(map) = result_map {
if let Some(val) = map.get(variable) {
return self.extract_property(val, property).unwrap_or(Value::Null);
}
}
for (i, group_expr) in group_by.expressions.iter().enumerate() {
if self.expressions_match(expr, group_expr) {
return group_key[i].clone().into();
}
}
group_elements
.first()
.map(|e| self.evaluate_value(expr, e))
.unwrap_or(Value::Null)
}
Expression::BinaryOp { left, op, right } => {
let left_val = self.evaluate_having_value(
left,
return_clause,
group_by,
group_key,
group_elements,
result_map,
);
let right_val = self.evaluate_having_value(
right,
return_clause,
group_by,
group_key,
group_elements,
result_map,
);
apply_binary_op(*op, left_val, right_val)
}
Expression::FunctionCall { name, args } => {
let element = group_elements.first().cloned().unwrap_or(Value::Null);
self.evaluate_function_call(name, args, &element)
}
_ => {
self.evaluate_group_expression(expr, group_by, group_key, group_elements)
.unwrap_or(Value::Null)
}
}
}
fn evaluate_having_predicate_multi_var(
&self,
expr: &Expression,
return_clause: &ReturnClause,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_traversers: &[crate::traversal::Traverser],
result_map: &Value,
) -> bool {
match expr {
Expression::BinaryOp { left, op, right } => {
match op {
BinaryOperator::And => {
self.evaluate_having_predicate_multi_var(
left,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
) && self.evaluate_having_predicate_multi_var(
right,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
)
}
BinaryOperator::Or => {
self.evaluate_having_predicate_multi_var(
left,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
) || self.evaluate_having_predicate_multi_var(
right,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
)
}
_ => {
let left_val = self.evaluate_having_value_multi_var(
left,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
);
let right_val = self.evaluate_having_value_multi_var(
right,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
);
apply_comparison(*op, &left_val, &right_val)
}
}
}
Expression::UnaryOp { op, expr: inner } => match op {
UnaryOperator::Not => !self.evaluate_having_predicate_multi_var(
inner,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
),
UnaryOperator::Neg => {
let val = self.evaluate_having_value_multi_var(
inner,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
);
match val {
Value::Int(n) => n == 0,
Value::Float(f) => f == 0.0,
Value::Bool(b) => !b,
Value::Null => true,
_ => false,
}
}
},
_ => {
let val = self.evaluate_having_value_multi_var(
expr,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
);
match val {
Value::Bool(b) => b,
Value::Null => false,
Value::Int(n) => n != 0,
Value::Float(f) => f != 0.0,
Value::String(s) => !s.is_empty(),
_ => true,
}
}
}
}
#[allow(clippy::only_used_in_recursion)]
fn evaluate_having_value_multi_var(
&self,
expr: &Expression,
return_clause: &ReturnClause,
group_by: &GroupByClause,
group_key: &[ComparableValue],
group_traversers: &[crate::traversal::Traverser],
result_map: &Value,
) -> Value {
match expr {
Expression::Literal(lit) => lit.clone().into(),
Expression::Variable(name) => {
if let Value::Map(map) = result_map {
if let Some(val) = map.get(name) {
return val.clone();
}
}
group_traversers
.first()
.map(|t| self.evaluate_value_from_path(expr, t))
.unwrap_or(Value::Null)
}
Expression::Aggregate {
func,
distinct,
expr: inner,
} => {
self.compute_aggregate_multi_var(*func, *distinct, inner, group_traversers)
.unwrap_or(Value::Null)
}
Expression::Property { variable, property } => {
if let Value::Map(map) = result_map {
if let Some(val) = map.get(variable) {
return self.extract_property(val, property).unwrap_or(Value::Null);
}
}
for (i, group_expr) in group_by.expressions.iter().enumerate() {
if self.expressions_match(expr, group_expr) {
return group_key[i].clone().into();
}
}
group_traversers
.first()
.map(|t| self.evaluate_value_from_path(expr, t))
.unwrap_or(Value::Null)
}
Expression::BinaryOp { left, op, right } => {
let left_val = self.evaluate_having_value_multi_var(
left,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
);
let right_val = self.evaluate_having_value_multi_var(
right,
return_clause,
group_by,
group_key,
group_traversers,
result_map,
);
apply_binary_op(*op, left_val, right_val)
}
Expression::FunctionCall { name, args } => {
let element = group_traversers
.first()
.map(|t| t.value.clone())
.unwrap_or(Value::Null);
self.evaluate_function_call(name, args, &element)
}
_ => {
self.evaluate_group_expression_multi_var(
expr,
group_by,
group_key,
group_traversers,
)
.unwrap_or(Value::Null)
}
}
}
fn execute_global_aggregates(
&self,
return_clause: &ReturnClause,
aggregates: &[(&ReturnItem, AggregateFunc, bool, &Expression)],
elements: &[Value],
having_clause: &Option<HavingClause>,
) -> Result<Vec<Value>, CompileError> {
let mut row_map = ValueMap::new();
for (item, func, distinct, expr) in aggregates {
let key = self.get_return_item_key(item);
let value = self.compute_aggregate(*func, *distinct, expr, elements)?;
row_map.insert(key, value);
}
let row_value = Value::Map(row_map);
if let Some(having) = having_clause {
let empty_group_by = GroupByClause {
expressions: Vec::new(),
};
if !self.evaluate_having_predicate(
&having.expression,
return_clause,
&empty_group_by,
&[],
elements,
&row_value,
) {
return Ok(Vec::new());
}
}
if aggregates.len() == 1 {
let (item, _, _, _) = &aggregates[0];
if item.alias.is_some() {
Ok(vec![row_value])
} else {
if let Value::Map(map) = row_value {
let key = self.get_return_item_key(item);
let value = map.get(&key).cloned().unwrap_or(Value::Null);
Ok(vec![value])
} else {
Ok(vec![row_value])
}
}
} else {
Ok(vec![row_value])
}
}
fn execute_grouped_aggregates(
&self,
return_clause: &ReturnClause,
group_by_items: &[(&ReturnItem, &Expression)],
aggregates: &[(&ReturnItem, AggregateFunc, bool, &Expression)],
elements: &[Value],
having_clause: &Option<HavingClause>,
) -> Result<Vec<Value>, CompileError> {
let mut groups: HashMap<Vec<ComparableValue>, Vec<&Value>> = HashMap::new();
for element in elements {
let group_key: Vec<ComparableValue> = group_by_items
.iter()
.map(|(_, expr)| {
let val = self.evaluate_value(expr, element);
ComparableValue::from(val)
})
.collect();
groups.entry(group_key).or_default().push(element);
}
let synthesized_group_by = having_clause.as_ref().map(|_| GroupByClause {
expressions: group_by_items
.iter()
.map(|(_, expr)| (*expr).clone())
.collect(),
});
let mut results = Vec::new();
for (group_key, group_elements) in groups {
let mut map = ValueMap::new();
for (i, (item, _expr)) in group_by_items.iter().enumerate() {
let key = self.get_return_item_key(item);
let value = group_key[i].clone().into();
map.insert(key, value);
}
let group_values: Vec<Value> = group_elements.into_iter().cloned().collect();
for (item, func, distinct, expr) in aggregates {
let key = self.get_return_item_key(item);
let value = self.compute_aggregate(*func, *distinct, expr, &group_values)?;
map.insert(key, value);
}
let row = Value::Map(map);
if let (Some(having), Some(gb)) = (having_clause, &synthesized_group_by) {
if !self.evaluate_having_predicate(
&having.expression,
return_clause,
gb,
&group_key,
&group_values,
&row,
) {
continue;
}
}
results.push(row);
}
Ok(results)
}
fn compute_aggregate(
&self,
func: AggregateFunc,
distinct: bool,
expr: &Expression,
elements: &[Value],
) -> Result<Value, CompileError> {
let is_count_star = matches!(func, AggregateFunc::Count)
&& matches!(expr, Expression::Variable(v) if v == "*");
if is_count_star {
return Ok(Value::Int(elements.len() as i64));
}
let mut values: Vec<Value> = elements
.iter()
.filter_map(|element| self.evaluate_expression(expr, element))
.collect();
if distinct {
let mut seen: Vec<ComparableValue> = Vec::new();
values.retain(|v| {
let comparable = ComparableValue::from(v.clone());
if seen.contains(&comparable) {
false
} else {
seen.push(comparable);
true
}
});
}
match func {
AggregateFunc::Count => Ok(Value::Int(values.len() as i64)),
AggregateFunc::Sum => {
let mut int_sum: i64 = 0;
let mut float_sum: f64 = 0.0;
let mut has_float = false;
for v in &values {
match v {
Value::Int(n) => int_sum += n,
Value::Float(f) => {
has_float = true;
float_sum += f;
}
_ => {} }
}
if has_float {
Ok(Value::Float(int_sum as f64 + float_sum))
} else {
Ok(Value::Int(int_sum))
}
}
AggregateFunc::Avg => {
let mut sum: f64 = 0.0;
let mut count: usize = 0;
for v in &values {
match v {
Value::Int(n) => {
sum += *n as f64;
count += 1;
}
Value::Float(f) => {
sum += f;
count += 1;
}
_ => {} }
}
if count > 0 {
Ok(Value::Float(sum / count as f64))
} else {
Ok(Value::Null)
}
}
AggregateFunc::Min => values
.into_iter()
.filter(|v| !matches!(v, Value::Null))
.min_by(compare_values)
.map(Ok)
.unwrap_or(Ok(Value::Null)),
AggregateFunc::Max => values
.into_iter()
.filter(|v| !matches!(v, Value::Null))
.max_by(compare_values)
.map(Ok)
.unwrap_or(Ok(Value::Null)),
AggregateFunc::Collect => Ok(Value::List(values)),
}
}
fn apply_order_by(
&self,
order_clause: &Option<OrderClause>,
return_clause: &ReturnClause,
mut results: Vec<Value>,
) -> Result<Vec<Value>, CompileError> {
let order = match order_clause {
Some(o) => o,
None => return Ok(results),
};
if order.items.is_empty() {
return Ok(results);
}
for order_item in order.items.iter().rev() {
let descending = order_item.descending;
let expr = &order_item.expression;
results.sort_by(|a, b| {
let key_a = self.extract_order_key(expr, a, return_clause);
let key_b = self.extract_order_key(expr, b, return_clause);
let cmp = compare_values(&key_a, &key_b);
if descending {
cmp.reverse()
} else {
cmp
}
});
}
Ok(results)
}
fn extract_order_key(
&self,
expr: &Expression,
result: &Value,
return_clause: &ReturnClause,
) -> Value {
match expr {
Expression::Property { variable, property } => {
match result {
Value::Map(map) => {
let full_key = format!("{}.{}", variable, property);
if let Some(val) = map.get(&full_key) {
return val.clone();
}
if let Some(val) = map.get(property) {
return val.clone();
}
for item in &return_clause.items {
if let Some(alias) = &item.alias {
if let Expression::Property {
variable: v,
property: p,
} = &item.expression
{
if v == variable && p == property {
if let Some(val) = map.get(alias) {
return val.clone();
}
}
}
}
}
Value::Null
}
Value::Vertex(id) => {
if let Some(vertex) = self.snapshot.storage().get_vertex(*id) {
vertex
.properties
.get(property)
.cloned()
.unwrap_or(Value::Null)
} else {
Value::Null
}
}
Value::Edge(id) => {
if let Some(edge) = self.snapshot.storage().get_edge(*id) {
edge.properties
.get(property)
.cloned()
.unwrap_or(Value::Null)
} else {
Value::Null
}
}
_ => result.clone(),
}
}
Expression::Variable(var) => {
if let Value::Map(map) = result {
if let Some(val) = map.get(var) {
return val.clone();
}
}
result.clone()
}
Expression::Literal(lit) => lit.clone().into(),
_ => Value::Null,
}
}
fn apply_limit(&self, limit_clause: &Option<LimitClause>, results: Vec<Value>) -> Vec<Value> {
let limit = match limit_clause {
Some(l) => l,
None => return results,
};
let offset = limit.offset.unwrap_or(0) as usize;
let count = limit.limit as usize;
results.into_iter().skip(offset).take(count).collect()
}
fn evaluate_math_from_row(&self, args: &[Expression], row: &HashMap<String, Value>) -> Value {
if args.is_empty() {
return Value::Null;
}
let expr_string = match self.evaluate_expression_from_row(&args[0], row) {
Value::String(s) => s,
_ => return Value::Null,
};
let mut var_values: Vec<f64> = Vec::new();
for arg in args.iter().skip(1) {
match self.evaluate_expression_from_row(arg, row) {
Value::Int(n) => var_values.push(n as f64),
Value::Float(f) => var_values.push(f),
_ => return Value::Null,
}
}
evaluate_math_expr_internal(&expr_string, &var_values)
}
fn evaluate_math_from_path(
&self,
args: &[Expression],
traverser: &crate::traversal::Traverser,
) -> Value {
if args.is_empty() {
return Value::Null;
}
let expr_string = match self.evaluate_value_from_path(&args[0], traverser) {
Value::String(s) => s,
_ => return Value::Null,
};
let mut var_values: Vec<f64> = Vec::new();
for arg in args.iter().skip(1) {
match self.evaluate_value_from_path(arg, traverser) {
Value::Int(n) => var_values.push(n as f64),
Value::Float(f) => var_values.push(f),
_ => return Value::Null,
}
}
evaluate_math_expr_internal(&expr_string, &var_values)
}
fn evaluate_math(&self, args: &[Expression], element: &Value) -> Value {
if args.is_empty() {
return Value::Null;
}
let expr_string = match self.evaluate_value(&args[0], element) {
Value::String(s) => s,
_ => return Value::Null,
};
let mut var_values: Vec<f64> = Vec::new();
for arg in args.iter().skip(1) {
match self.evaluate_value(arg, element) {
Value::Int(n) => var_values.push(n as f64),
Value::Float(f) => var_values.push(f),
_ => return Value::Null,
}
}
evaluate_math_expr_internal(&expr_string, &var_values)
}
}
use super::helpers::{
apply_binary_op, apply_comparison, compare_values, eval_inline_predicate, ComparableValue,
};
use super::math::evaluate_math_expr_internal;