Skip to main content

petgraph_decypher/
query.rs

1//! Query execution engine for running Cypher queries against a petgraph.
2
3use std::collections::HashMap;
4
5use itertools::Itertools;
6use petgraph::Graph;
7use petgraph::graph::{EdgeIndex, EdgeReference, NodeIndex};
8use petgraph::visit::EdgeRef;
9
10use crate::ast::*;
11use crate::error::CypherError;
12use crate::{CypherEdge, CypherNode, EdgeData, NodeData};
13
14/// Strategy to use when matching patterns against the graph.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16pub enum MatchStrategy {
17    /// DFS backtracking (Neo4j-accurate semantics). Default strategy.
18    #[default]
19    Backtrack,
20    /// Uses petgraph-native algorithms (BFS/DFS, faster but less accurate).
21    /// Currently delegates to Backtrack; a distinct optimized implementation is planned.
22    Fast,
23}
24
25/// A value that can appear in a query result row.
26#[derive(Debug, Clone, PartialEq)]
27pub enum ResultValue {
28    /// A scalar property value (including List and Map).
29    Scalar(CypherValue),
30    /// A matched node with its labels and properties.
31    Node {
32        labels: Vec<String>,
33        properties: HashMap<String, CypherValue>,
34    },
35    /// A matched edge with its type and properties.
36    Edge {
37        rel_type: Option<String>,
38        properties: HashMap<String, CypherValue>,
39    },
40}
41
42/// A single row of query results.
43#[derive(Debug, Clone, PartialEq)]
44pub struct Row {
45    pub values: HashMap<String, ResultValue>,
46}
47
48/// A bound value in the query execution context.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
50enum BoundValue {
51    Node(NodeIndex),
52    Edge(EdgeIndex),
53}
54
55/// A set of variable bindings from pattern matching.
56type Bindings = HashMap<String, BoundValue>;
57/// Query parameters mapped by name (without the leading `$`).
58///
59/// For example, query text `$name` is resolved from the `"name"` key.
60pub type Parameters = HashMap<String, CypherValue>;
61
62/// An iterator over query result rows.
63///
64/// Both the MATCH and RETURN projection phases are executed eagerly.
65/// The returned iterator streams over the fully materialized result rows.
66pub struct QueryResult<'a, N = NodeData, E = EdgeData> {
67    columns: Vec<String>,
68    rows: std::vec::IntoIter<Row>,
69    _graph: &'a Graph<N, E>,
70}
71
72impl<'a, N, E> QueryResult<'a, N, E> {
73    fn new(columns: Vec<String>, rows: Vec<Row>, graph: &'a Graph<N, E>) -> Self {
74        Self {
75            columns,
76            rows: rows.into_iter(),
77            _graph: graph,
78        }
79    }
80
81    pub fn columns(&self) -> &[String] {
82        &self.columns
83    }
84}
85
86impl<'a, N, E> Iterator for QueryResult<'a, N, E> {
87    type Item = Row;
88
89    fn next(&mut self) -> Option<Row> {
90        self.rows.next()
91    }
92
93    fn size_hint(&self) -> (usize, Option<usize>) {
94        self.rows.size_hint()
95    }
96}
97
98/// Extension trait for executing Cypher queries on a [`Graph<NodeData, EdgeData>`].
99pub trait PetgraphCypher {
100    /// Execute a read-only Cypher query and stream the results.
101    ///
102    /// Supports `MATCH`, `WHERE`, and `RETURN` clauses.
103    /// Returns an error if mutation clauses (`CREATE`, `MERGE`, `DELETE`) are present.
104    ///
105    /// Uses the default `Backtrack` match strategy.
106    fn cypher(&self, query: &str) -> Result<QueryResult<'_>, CypherError>;
107
108    /// Execute a read-only Cypher query and stream the results using parameters.
109    fn cypher_params(
110        &self,
111        query: &str,
112        parameters: &Parameters,
113    ) -> Result<QueryResult<'_>, CypherError> {
114        if parameters.is_empty() {
115            self.cypher(query)
116        } else {
117            Err(CypherError::Unsupported(
118                "parameterized queries are not supported by this PetgraphCypher implementor".into(),
119            ))
120        }
121    }
122
123    /// Execute a mutating Cypher query.
124    ///
125    /// Supports `MATCH`, `WHERE`, `CREATE`, `MERGE`, and `DELETE` clauses.
126    /// Returns an error if a `RETURN` clause is present — use [`Self::cypher`] for reads.
127    fn cypher_mut(&mut self, query: &str) -> Result<(), CypherError>;
128
129    /// Execute a read-only Cypher query with a specific match strategy.
130    fn cypher_with_strategy(
131        &self,
132        query: &str,
133        strategy: MatchStrategy,
134    ) -> Result<QueryResult<'_>, CypherError>;
135
136    /// Execute a read-only Cypher query with a specific match strategy and parameters.
137    fn cypher_with_strategy_params(
138        &self,
139        query: &str,
140        strategy: MatchStrategy,
141        parameters: &Parameters,
142    ) -> Result<QueryResult<'_>, CypherError> {
143        if parameters.is_empty() {
144            self.cypher_with_strategy(query, strategy)
145        } else {
146            Err(CypherError::Unsupported(
147                "parameterized queries are not supported by this PetgraphCypher implementor".into(),
148            ))
149        }
150    }
151}
152
153/// Extension trait for executing read-only Cypher queries on a
154/// [`Graph`] with custom node and edge weights.
155pub trait PetgraphCypherRead<N: CypherNode, E: CypherEdge> {
156    /// Execute a read-only Cypher query and stream the results.
157    ///
158    /// Supports `MATCH`, `WHERE`, and `RETURN` clauses.
159    /// Returns an error if mutation clauses (`CREATE`, `MERGE`, `DELETE`) are present.
160    ///
161    /// Uses the default `Backtrack` match strategy.
162    fn cypher(&self, query: &str) -> Result<QueryResult<'_, N, E>, CypherError>;
163
164    /// Execute a read-only Cypher query and stream the results using parameters.
165    fn cypher_params(
166        &self,
167        query: &str,
168        parameters: &Parameters,
169    ) -> Result<QueryResult<'_, N, E>, CypherError> {
170        if parameters.is_empty() {
171            self.cypher(query)
172        } else {
173            Err(CypherError::Unsupported(
174                "parameterized queries are not supported by this PetgraphCypherRead implementor"
175                    .into(),
176            ))
177        }
178    }
179
180    /// Execute a read-only Cypher query with a specific match strategy.
181    fn cypher_with_strategy(
182        &self,
183        query: &str,
184        strategy: MatchStrategy,
185    ) -> Result<QueryResult<'_, N, E>, CypherError>;
186
187    /// Execute a read-only Cypher query with a specific match strategy and parameters.
188    fn cypher_with_strategy_params(
189        &self,
190        query: &str,
191        strategy: MatchStrategy,
192        parameters: &Parameters,
193    ) -> Result<QueryResult<'_, N, E>, CypherError> {
194        if parameters.is_empty() {
195            self.cypher_with_strategy(query, strategy)
196        } else {
197            Err(CypherError::Unsupported(
198                "parameterized queries are not supported by this PetgraphCypherRead implementor"
199                    .into(),
200            ))
201        }
202    }
203}
204
205impl<N: CypherNode, E: CypherEdge> PetgraphCypherRead<N, E> for Graph<N, E> {
206    fn cypher(&self, query: &str) -> Result<QueryResult<'_, N, E>, CypherError> {
207        let params = Parameters::new();
208        self.cypher_with_strategy_params(query, MatchStrategy::default(), &params)
209    }
210
211    fn cypher_params(
212        &self,
213        query: &str,
214        parameters: &Parameters,
215    ) -> Result<QueryResult<'_, N, E>, CypherError> {
216        self.cypher_with_strategy_params(query, MatchStrategy::default(), parameters)
217    }
218
219    fn cypher_with_strategy(
220        &self,
221        query: &str,
222        strategy: MatchStrategy,
223    ) -> Result<QueryResult<'_, N, E>, CypherError> {
224        let params = Parameters::new();
225        self.cypher_with_strategy_params(query, strategy, &params)
226    }
227
228    fn cypher_with_strategy_params(
229        &self,
230        query: &str,
231        strategy: MatchStrategy,
232        parameters: &Parameters,
233    ) -> Result<QueryResult<'_, N, E>, CypherError> {
234        let ast = crate::parse_cypher(query)?;
235        validate_read_query(&ast)?;
236        validate_read_query_parameters(&ast, parameters)?;
237        ReadQueryExecutor::new(self, strategy, parameters).execute(ast)
238    }
239}
240
241impl PetgraphCypher for Graph<NodeData, EdgeData> {
242    fn cypher(&self, query: &str) -> Result<QueryResult<'_>, CypherError> {
243        let params = Parameters::new();
244        PetgraphCypher::cypher_with_strategy_params(self, query, MatchStrategy::default(), &params)
245    }
246
247    fn cypher_params(
248        &self,
249        query: &str,
250        parameters: &Parameters,
251    ) -> Result<QueryResult<'_>, CypherError> {
252        PetgraphCypher::cypher_with_strategy_params(
253            self,
254            query,
255            MatchStrategy::default(),
256            parameters,
257        )
258    }
259
260    fn cypher_mut(&mut self, query: &str) -> Result<(), CypherError> {
261        let ast = crate::parse_cypher(query)?;
262        validate_mutation_query(&ast)?;
263        validate_mutation_query_parameters(&ast)?;
264        MutQueryExecutor::new(self).execute(ast)
265    }
266
267    fn cypher_with_strategy(
268        &self,
269        query: &str,
270        strategy: MatchStrategy,
271    ) -> Result<QueryResult<'_>, CypherError> {
272        let params = Parameters::new();
273        PetgraphCypher::cypher_with_strategy_params(self, query, strategy, &params)
274    }
275
276    fn cypher_with_strategy_params(
277        &self,
278        query: &str,
279        strategy: MatchStrategy,
280        parameters: &Parameters,
281    ) -> Result<QueryResult<'_>, CypherError> {
282        let ast = crate::parse_cypher(query)?;
283        validate_read_query(&ast)?;
284        validate_read_query_parameters(&ast, parameters)?;
285        ReadQueryExecutor::new(self, strategy, parameters).execute(ast)
286    }
287}
288
289/// Validate that a query only contains read clauses.
290fn validate_read_query(query: &CypherQuery) -> Result<(), CypherError> {
291    for clause in &query.clauses {
292        match clause {
293            Clause::Create { .. } => {
294                return Err(CypherError::Unsupported(
295                    "CREATE not allowed in cypher(); use cypher_mut()".into(),
296                ));
297            }
298            Clause::Merge { .. } => {
299                return Err(CypherError::Unsupported(
300                    "MERGE not allowed in cypher(); use cypher_mut()".into(),
301                ));
302            }
303            Clause::Delete { .. } => {
304                return Err(CypherError::Unsupported(
305                    "DELETE not allowed in cypher(); use cypher_mut()".into(),
306                ));
307            }
308            Clause::Set { .. } | Clause::Remove { .. } => {
309                return Err(CypherError::Unsupported(
310                    "SET/REMOVE not allowed in cypher(); use cypher_mut()".into(),
311                ));
312            }
313            Clause::With { .. } => {
314                return Err(CypherError::Unsupported("WITH is not yet supported".into()));
315            }
316            _ => {}
317        }
318    }
319    Ok(())
320}
321
322/// Validate that a mutation query does not contain read clauses.
323fn validate_mutation_query(query: &CypherQuery) -> Result<(), CypherError> {
324    for clause in &query.clauses {
325        if matches!(clause, Clause::Return { .. }) {
326            return Err(CypherError::Unsupported(
327                "RETURN not allowed in cypher_mut(); use cypher() for reads".into(),
328            ));
329        }
330    }
331    Ok(())
332}
333
334fn ensure_expression_parameters(
335    expression: &Expression,
336    parameters: &Parameters,
337) -> Result<(), CypherError> {
338    match expression {
339        Expression::Unary(_, inner) => ensure_expression_parameters(inner, parameters),
340        Expression::Binary(_, left, right) => {
341            ensure_expression_parameters(left, parameters)?;
342            ensure_expression_parameters(right, parameters)
343        }
344        Expression::List(items) => {
345            for item in items {
346                ensure_expression_parameters(item, parameters)?;
347            }
348            Ok(())
349        }
350        Expression::FunctionCall { args, .. } => {
351            for arg in args {
352                ensure_expression_parameters(arg, parameters)?;
353            }
354            Ok(())
355        }
356        Expression::Parameter(name) => {
357            if parameters.contains_key(name) {
358                Ok(())
359            } else {
360                Err(CypherError::InvalidQuery(format!(
361                    "missing query parameter: ${name}"
362                )))
363            }
364        }
365        Expression::Case(case) => {
366            if let Some(scrutinee) = &case.scrutinee {
367                ensure_expression_parameters(scrutinee, parameters)?;
368            }
369            for (when, then) in &case.alternatives {
370                ensure_expression_parameters(when, parameters)?;
371                ensure_expression_parameters(then, parameters)?;
372            }
373            if let Some(default) = &case.default {
374                ensure_expression_parameters(default, parameters)?;
375            }
376            Ok(())
377        }
378        Expression::Variable(_)
379        | Expression::Property(_, _)
380        | Expression::All
381        | Expression::Literal(_) => Ok(()),
382    }
383}
384
385fn ensure_where_parameters(
386    where_expr: &WhereExpr,
387    parameters: &Parameters,
388) -> Result<(), CypherError> {
389    match where_expr {
390        WhereExpr::Eq(left, right)
391        | WhereExpr::NotEq(left, right)
392        | WhereExpr::Lt(left, right)
393        | WhereExpr::Gt(left, right)
394        | WhereExpr::Le(left, right)
395        | WhereExpr::Ge(left, right) => {
396            ensure_expression_parameters(left, parameters)?;
397            ensure_expression_parameters(right, parameters)
398        }
399        WhereExpr::In(expression, _)
400        | WhereExpr::StartsWith(expression, _)
401        | WhereExpr::EndsWith(expression, _)
402        | WhereExpr::Contains(expression, _)
403        | WhereExpr::IsNull(expression)
404        | WhereExpr::IsNotNull(expression) => ensure_expression_parameters(expression, parameters),
405        WhereExpr::Not(inner) => ensure_where_parameters(inner, parameters),
406        WhereExpr::And(left, right) | WhereExpr::Or(left, right) | WhereExpr::Xor(left, right) => {
407            ensure_where_parameters(left, parameters)?;
408            ensure_where_parameters(right, parameters)
409        }
410    }
411}
412
413fn validate_read_query_parameters(
414    query: &CypherQuery,
415    parameters: &Parameters,
416) -> Result<(), CypherError> {
417    for clause in &query.clauses {
418        match clause {
419            Clause::Match { where_clause, .. } | Clause::OptionalMatch { where_clause, .. } => {
420                if let Some(where_expr) = where_clause {
421                    ensure_where_parameters(where_expr, parameters)?;
422                }
423            }
424            Clause::Return { items, .. } => {
425                for item in items {
426                    ensure_expression_parameters(&item.expression, parameters)?;
427                }
428            }
429            Clause::Unwind { expression, .. } => {
430                ensure_expression_parameters(expression, parameters)?;
431            }
432            Clause::OrderBy { items } => {
433                for item in items {
434                    ensure_expression_parameters(&item.expression, parameters)?;
435                }
436            }
437            Clause::Create { .. }
438            | Clause::With { .. }
439            | Clause::Set { .. }
440            | Clause::Merge { .. }
441            | Clause::Delete { .. }
442            | Clause::Remove { .. }
443            | Clause::Skip { .. }
444            | Clause::Limit { .. } => {}
445        }
446    }
447    Ok(())
448}
449
450fn ensure_expression_has_no_parameters(expression: &Expression) -> Result<(), CypherError> {
451    match expression {
452        Expression::Unary(_, inner) => ensure_expression_has_no_parameters(inner),
453        Expression::Binary(_, left, right) => {
454            ensure_expression_has_no_parameters(left)?;
455            ensure_expression_has_no_parameters(right)
456        }
457        Expression::List(items) => {
458            for item in items {
459                ensure_expression_has_no_parameters(item)?;
460            }
461            Ok(())
462        }
463        Expression::FunctionCall { args, .. } => {
464            for arg in args {
465                ensure_expression_has_no_parameters(arg)?;
466            }
467            Ok(())
468        }
469        Expression::Parameter(name) => Err(CypherError::Unsupported(format!(
470            "query parameters are not supported in cypher_mut(): ${name}"
471        ))),
472        Expression::Case(case) => {
473            if let Some(scrutinee) = &case.scrutinee {
474                ensure_expression_has_no_parameters(scrutinee)?;
475            }
476            for (when, then) in &case.alternatives {
477                ensure_expression_has_no_parameters(when)?;
478                ensure_expression_has_no_parameters(then)?;
479            }
480            if let Some(default) = &case.default {
481                ensure_expression_has_no_parameters(default)?;
482            }
483            Ok(())
484        }
485        Expression::Variable(_)
486        | Expression::Property(_, _)
487        | Expression::All
488        | Expression::Literal(_) => Ok(()),
489    }
490}
491
492fn ensure_where_has_no_parameters(where_expr: &WhereExpr) -> Result<(), CypherError> {
493    match where_expr {
494        WhereExpr::Eq(left, right)
495        | WhereExpr::NotEq(left, right)
496        | WhereExpr::Lt(left, right)
497        | WhereExpr::Gt(left, right)
498        | WhereExpr::Le(left, right)
499        | WhereExpr::Ge(left, right) => {
500            ensure_expression_has_no_parameters(left)?;
501            ensure_expression_has_no_parameters(right)
502        }
503        WhereExpr::In(expression, _)
504        | WhereExpr::StartsWith(expression, _)
505        | WhereExpr::EndsWith(expression, _)
506        | WhereExpr::Contains(expression, _)
507        | WhereExpr::IsNull(expression)
508        | WhereExpr::IsNotNull(expression) => ensure_expression_has_no_parameters(expression),
509        WhereExpr::Not(inner) => ensure_where_has_no_parameters(inner),
510        WhereExpr::And(left, right) | WhereExpr::Or(left, right) | WhereExpr::Xor(left, right) => {
511            ensure_where_has_no_parameters(left)?;
512            ensure_where_has_no_parameters(right)
513        }
514    }
515}
516
517fn ensure_set_item_has_no_parameters(item: &SetItem) -> Result<(), CypherError> {
518    match item {
519        SetItem::SetProperty { value, .. } => ensure_expression_has_no_parameters(value),
520        SetItem::SetVariable { .. }
521        | SetItem::SetLabels { .. }
522        | SetItem::MergeProperties { .. } => Ok(()),
523    }
524}
525
526fn validate_mutation_query_parameters(query: &CypherQuery) -> Result<(), CypherError> {
527    for clause in &query.clauses {
528        match clause {
529            Clause::Match { where_clause, .. } | Clause::OptionalMatch { where_clause, .. } => {
530                if let Some(where_expr) = where_clause {
531                    ensure_where_has_no_parameters(where_expr)?;
532                }
533            }
534            Clause::Set { items } => {
535                for item in items {
536                    ensure_set_item_has_no_parameters(item)?;
537                }
538            }
539            Clause::Merge {
540                on_create,
541                on_match,
542                ..
543            } => {
544                for item in on_create {
545                    ensure_set_item_has_no_parameters(item)?;
546                }
547                for item in on_match {
548                    ensure_set_item_has_no_parameters(item)?;
549                }
550            }
551            Clause::Unwind { expression, .. } => ensure_expression_has_no_parameters(expression)?,
552            Clause::Create { .. }
553            | Clause::Delete { .. }
554            | Clause::Remove { .. }
555            | Clause::Return { .. }
556            | Clause::With { .. }
557            | Clause::OrderBy { .. }
558            | Clause::Skip { .. }
559            | Clause::Limit { .. } => {}
560        }
561    }
562    Ok(())
563}
564
565// ---------------------------------------------------------------------------
566// Expression evaluation helpers
567// ---------------------------------------------------------------------------
568
569fn is_truthy(value: &CypherValue) -> Option<bool> {
570    match value {
571        CypherValue::Null => None,
572        CypherValue::Boolean(b) => Some(*b),
573        _ => Some(true),
574    }
575}
576
577fn compare_cypher_values(left: &CypherValue, right: &CypherValue) -> Option<std::cmp::Ordering> {
578    match (left, right) {
579        (CypherValue::Null, _) | (_, CypherValue::Null) => None,
580        (CypherValue::Boolean(a), CypherValue::Boolean(b)) => a.partial_cmp(b),
581        (CypherValue::Integer(a), CypherValue::Integer(b)) => a.partial_cmp(b),
582        (CypherValue::Integer(a), CypherValue::Float(b)) => (*a as f64).partial_cmp(b),
583        (CypherValue::Float(a), CypherValue::Integer(b)) => a.partial_cmp(&(*b as f64)),
584        (CypherValue::Float(a), CypherValue::Float(b)) => a.partial_cmp(b),
585        (CypherValue::String(a), CypherValue::String(b)) => a.partial_cmp(b),
586        (CypherValue::List(a), CypherValue::List(b)) => a.partial_cmp(b),
587        _ => None,
588    }
589}
590
591fn cypher_value_eq(left: &CypherValue, right: &CypherValue) -> CypherValue {
592    match (left, right) {
593        (CypherValue::Null, _) | (_, CypherValue::Null) => CypherValue::Null,
594        _ => CypherValue::Boolean(left == right),
595    }
596}
597
598fn evaluate_binary_op(
599    op: BinaryOp,
600    left: CypherValue,
601    right: CypherValue,
602) -> Result<CypherValue, CypherError> {
603    match op {
604        BinaryOp::Add => match (&left, &right) {
605            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a + b)),
606            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a + b)),
607            (CypherValue::Integer(a), CypherValue::Float(b)) => {
608                Ok(CypherValue::Float(*a as f64 + b))
609            }
610            (CypherValue::Float(a), CypherValue::Integer(b)) => {
611                Ok(CypherValue::Float(a + *b as f64))
612            }
613            (CypherValue::String(a), CypherValue::String(b)) => {
614                Ok(CypherValue::String(format!("{}{}", a, b)))
615            }
616            (CypherValue::List(a), CypherValue::List(b)) => {
617                let mut result = a.clone();
618                result.extend(b.clone());
619                Ok(CypherValue::List(result))
620            }
621            _ => Err(CypherError::TypeMismatch(format!(
622                "cannot add {:?} and {:?}",
623                left, right
624            ))),
625        },
626        BinaryOp::Subtract => match (&left, &right) {
627            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a - b)),
628            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a - b)),
629            (CypherValue::Integer(a), CypherValue::Float(b)) => {
630                Ok(CypherValue::Float(*a as f64 - b))
631            }
632            (CypherValue::Float(a), CypherValue::Integer(b)) => {
633                Ok(CypherValue::Float(a - *b as f64))
634            }
635            _ => Err(CypherError::TypeMismatch(format!(
636                "cannot subtract {:?} and {:?}",
637                left, right
638            ))),
639        },
640        BinaryOp::Multiply => match (&left, &right) {
641            (CypherValue::Integer(a), CypherValue::Integer(b)) => Ok(CypherValue::Integer(a * b)),
642            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a * b)),
643            (CypherValue::Integer(a), CypherValue::Float(b)) => {
644                Ok(CypherValue::Float(*a as f64 * b))
645            }
646            (CypherValue::Float(a), CypherValue::Integer(b)) => {
647                Ok(CypherValue::Float(a * *b as f64))
648            }
649            _ => Err(CypherError::TypeMismatch(format!(
650                "cannot multiply {:?} and {:?}",
651                left, right
652            ))),
653        },
654        BinaryOp::Divide => match (&left, &right) {
655            (CypherValue::Integer(a), CypherValue::Integer(b)) => {
656                if *b == 0 {
657                    return Err(CypherError::DivisionByZero);
658                }
659                Ok(CypherValue::Float(*a as f64 / *b as f64))
660            }
661            (CypherValue::Float(a), CypherValue::Float(b)) => {
662                if *b == 0.0 {
663                    return Err(CypherError::DivisionByZero);
664                }
665                Ok(CypherValue::Float(a / b))
666            }
667            (CypherValue::Integer(a), CypherValue::Float(b)) => {
668                if *b == 0.0 {
669                    return Err(CypherError::DivisionByZero);
670                }
671                Ok(CypherValue::Float(*a as f64 / b))
672            }
673            (CypherValue::Float(a), CypherValue::Integer(b)) => {
674                if *b == 0 {
675                    return Err(CypherError::DivisionByZero);
676                }
677                Ok(CypherValue::Float(a / *b as f64))
678            }
679            _ => Err(CypherError::TypeMismatch(format!(
680                "cannot divide {:?} by {:?}",
681                left, right
682            ))),
683        },
684        BinaryOp::Modulo => match (&left, &right) {
685            (CypherValue::Integer(a), CypherValue::Integer(b)) => {
686                if *b == 0 {
687                    return Err(CypherError::DivisionByZero);
688                }
689                Ok(CypherValue::Integer(a % b))
690            }
691            _ => Err(CypherError::TypeMismatch(format!(
692                "cannot modulo {:?} and {:?}",
693                left, right
694            ))),
695        },
696        BinaryOp::Power => match (&left, &right) {
697            (CypherValue::Integer(a), CypherValue::Integer(b)) => {
698                if *b < 0 {
699                    return Err(CypherError::TypeMismatch(
700                        "negative integer exponents are not supported for integer base; use a float base".into(),
701                    ));
702                }
703                if *b > u32::MAX as i64 {
704                    return Err(CypherError::TypeMismatch(
705                        "integer exponent is too large".into(),
706                    ));
707                }
708                Ok(CypherValue::Integer(a.pow(*b as u32)))
709            }
710            (CypherValue::Float(a), CypherValue::Float(b)) => Ok(CypherValue::Float(a.powf(*b))),
711            (CypherValue::Integer(a), CypherValue::Float(b)) => {
712                Ok(CypherValue::Float((*a as f64).powf(*b)))
713            }
714            (CypherValue::Float(a), CypherValue::Integer(b)) => {
715                Ok(CypherValue::Float(a.powf(*b as f64)))
716            }
717            _ => Err(CypherError::TypeMismatch(format!(
718                "cannot power {:?} and {:?}",
719                left, right
720            ))),
721        },
722        BinaryOp::Eq => Ok(cypher_value_eq(&left, &right)),
723        BinaryOp::Ne => {
724            let eq = cypher_value_eq(&left, &right);
725            match eq {
726                CypherValue::Null => Ok(CypherValue::Null),
727                CypherValue::Boolean(b) => Ok(CypherValue::Boolean(!b)),
728                _ => unreachable!(),
729            }
730        }
731        BinaryOp::Lt => Ok(match compare_cypher_values(&left, &right) {
732            None => CypherValue::Null,
733            Some(ord) => CypherValue::Boolean(ord == std::cmp::Ordering::Less),
734        }),
735        BinaryOp::Gt => Ok(match compare_cypher_values(&left, &right) {
736            None => CypherValue::Null,
737            Some(ord) => CypherValue::Boolean(ord == std::cmp::Ordering::Greater),
738        }),
739        BinaryOp::Le => Ok(match compare_cypher_values(&left, &right) {
740            None => CypherValue::Null,
741            Some(ord) => CypherValue::Boolean(ord != std::cmp::Ordering::Greater),
742        }),
743        BinaryOp::Ge => Ok(match compare_cypher_values(&left, &right) {
744            None => CypherValue::Null,
745            Some(ord) => CypherValue::Boolean(ord != std::cmp::Ordering::Less),
746        }),
747        BinaryOp::And => match (is_truthy(&left), is_truthy(&right)) {
748            (Some(false), _) | (_, Some(false)) => Ok(CypherValue::Boolean(false)),
749            (Some(true), Some(true)) => Ok(CypherValue::Boolean(true)),
750            _ => Ok(CypherValue::Null),
751        },
752        BinaryOp::Or => match (is_truthy(&left), is_truthy(&right)) {
753            (Some(true), _) | (_, Some(true)) => Ok(CypherValue::Boolean(true)),
754            (Some(false), Some(false)) => Ok(CypherValue::Boolean(false)),
755            _ => Ok(CypherValue::Null),
756        },
757        BinaryOp::Xor => match (is_truthy(&left), is_truthy(&right)) {
758            (Some(a), Some(b)) => Ok(CypherValue::Boolean(a ^ b)),
759            _ => Ok(CypherValue::Null),
760        },
761        BinaryOp::StartsWith => match (&left, &right) {
762            (CypherValue::String(a), CypherValue::String(b)) => {
763                Ok(CypherValue::Boolean(a.starts_with(b)))
764            }
765            (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
766            _ => Err(CypherError::TypeMismatch(format!(
767                "STARTS WITH requires strings, got {:?} and {:?}",
768                left, right
769            ))),
770        },
771        BinaryOp::EndsWith => match (&left, &right) {
772            (CypherValue::String(a), CypherValue::String(b)) => {
773                Ok(CypherValue::Boolean(a.ends_with(b)))
774            }
775            (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
776            _ => Err(CypherError::TypeMismatch(format!(
777                "ENDS WITH requires strings, got {:?} and {:?}",
778                left, right
779            ))),
780        },
781        BinaryOp::Contains => match (&left, &right) {
782            (CypherValue::String(a), CypherValue::String(b)) => {
783                Ok(CypherValue::Boolean(a.contains(b)))
784            }
785            (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
786            _ => Err(CypherError::TypeMismatch(format!(
787                "CONTAINS requires strings, got {:?} and {:?}",
788                left, right
789            ))),
790        },
791        BinaryOp::In => match (&right, &left) {
792            (CypherValue::List(list), _) => {
793                let found = list.iter().any(|item| item == &left);
794                Ok(CypherValue::Boolean(found))
795            }
796            (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
797            _ => Err(CypherError::TypeMismatch(format!(
798                "IN requires a list on the right side, got {:?}",
799                right
800            ))),
801        },
802    }
803}
804
805fn evaluate_function(
806    name: &str,
807    args: &[Expression],
808    _distinct: bool,
809    bindings: &Bindings,
810    graph: &Graph<impl CypherNode, impl CypherEdge>,
811    parameters: &Parameters,
812) -> Result<CypherValue, CypherError> {
813    // Helper to evaluate all args.
814    let eval_args = || {
815        args.iter()
816            .map(|arg| evaluate_expression(arg, bindings, graph, parameters))
817            .collect::<Result<Vec<_>, _>>()
818    };
819
820    match name.to_lowercase().as_str() {
821        "tostring" => {
822            let vals = eval_args()?;
823            if vals.len() != 1 {
824                return Err(CypherError::InvalidQuery(
825                    "toString() takes exactly 1 argument".into(),
826                ));
827            }
828            let s = match &vals[0] {
829                CypherValue::String(s) => s.clone(),
830                CypherValue::Integer(i) => i.to_string(),
831                CypherValue::Float(f) => f.to_string(),
832                CypherValue::Boolean(b) => b.to_string(),
833                CypherValue::Null => "null".to_string(),
834                CypherValue::List(_) | CypherValue::Map(_) => {
835                    return Err(CypherError::TypeMismatch(
836                        "toString() does not support lists or maps".into(),
837                    ));
838                }
839            };
840            Ok(CypherValue::String(s))
841        }
842        "tointeger" | "toint" => {
843            let vals = eval_args()?;
844            if vals.len() != 1 {
845                return Err(CypherError::InvalidQuery(
846                    "toInteger() takes exactly 1 argument".into(),
847                ));
848            }
849            match &vals[0] {
850                CypherValue::Integer(i) => Ok(CypherValue::Integer(*i)),
851                CypherValue::Float(f) => Ok(CypherValue::Integer(*f as i64)),
852                CypherValue::String(s) => {
853                    s.parse::<i64>().map(CypherValue::Integer).map_err(|_| {
854                        CypherError::TypeMismatch(format!("cannot convert '{}' to integer", s))
855                    })
856                }
857                CypherValue::Boolean(true) => Ok(CypherValue::Integer(1)),
858                CypherValue::Boolean(false) => Ok(CypherValue::Integer(0)),
859                CypherValue::Null => Ok(CypherValue::Null),
860                _ => Err(CypherError::TypeMismatch(format!(
861                    "cannot convert {:?} to integer",
862                    vals[0]
863                ))),
864            }
865        }
866        "tofloat" => {
867            let vals = eval_args()?;
868            if vals.len() != 1 {
869                return Err(CypherError::InvalidQuery(
870                    "toFloat() takes exactly 1 argument".into(),
871                ));
872            }
873            match &vals[0] {
874                CypherValue::Float(f) => Ok(CypherValue::Float(*f)),
875                CypherValue::Integer(i) => Ok(CypherValue::Float(*i as f64)),
876                CypherValue::String(s) => s.parse::<f64>().map(CypherValue::Float).map_err(|_| {
877                    CypherError::TypeMismatch(format!("cannot convert '{}' to float", s))
878                }),
879                CypherValue::Boolean(true) => Ok(CypherValue::Float(1.0)),
880                CypherValue::Boolean(false) => Ok(CypherValue::Float(0.0)),
881                CypherValue::Null => Ok(CypherValue::Null),
882                _ => Err(CypherError::TypeMismatch(format!(
883                    "cannot convert {:?} to float",
884                    vals[0]
885                ))),
886            }
887        }
888        "coalesce" => {
889            for arg in args {
890                let val = evaluate_expression(arg, bindings, graph, parameters)?;
891                if !matches!(val, CypherValue::Null) {
892                    return Ok(val);
893                }
894            }
895            Ok(CypherValue::Null)
896        }
897        "head" => {
898            let vals = eval_args()?;
899            if vals.len() != 1 {
900                return Err(CypherError::InvalidQuery(
901                    "head() takes exactly 1 argument".into(),
902                ));
903            }
904            match &vals[0] {
905                CypherValue::List(list) => Ok(list.first().cloned().unwrap_or(CypherValue::Null)),
906                CypherValue::Null => Ok(CypherValue::Null),
907                _ => Err(CypherError::TypeMismatch(format!(
908                    "head() requires a list, got {:?}",
909                    vals[0]
910                ))),
911            }
912        }
913        "last" => {
914            let vals = eval_args()?;
915            if vals.len() != 1 {
916                return Err(CypherError::InvalidQuery(
917                    "last() takes exactly 1 argument".into(),
918                ));
919            }
920            match &vals[0] {
921                CypherValue::List(list) => Ok(list.last().cloned().unwrap_or(CypherValue::Null)),
922                CypherValue::Null => Ok(CypherValue::Null),
923                _ => Err(CypherError::TypeMismatch(format!(
924                    "last() requires a list, got {:?}",
925                    vals[0]
926                ))),
927            }
928        }
929        "size" | "length" => {
930            let vals = eval_args()?;
931            if vals.len() != 1 {
932                return Err(CypherError::InvalidQuery(format!(
933                    "{}() takes exactly 1 argument",
934                    name
935                )));
936            }
937            match &vals[0] {
938                CypherValue::List(list) => Ok(CypherValue::Integer(list.len() as i64)),
939                CypherValue::String(s) => Ok(CypherValue::Integer(s.len() as i64)),
940                CypherValue::Null => Ok(CypherValue::Null),
941                _ => Err(CypherError::TypeMismatch(format!(
942                    "{}() requires a list or string, got {:?}",
943                    name, vals[0]
944                ))),
945            }
946        }
947        "toupper" => {
948            let vals = eval_args()?;
949            if vals.len() != 1 {
950                return Err(CypherError::InvalidQuery(
951                    "toUpper() takes exactly 1 argument".into(),
952                ));
953            }
954            match &vals[0] {
955                CypherValue::String(s) => Ok(CypherValue::String(s.to_uppercase())),
956                CypherValue::Null => Ok(CypherValue::Null),
957                _ => Err(CypherError::TypeMismatch(format!(
958                    "toUpper() requires a string, got {:?}",
959                    vals[0]
960                ))),
961            }
962        }
963        "tolower" => {
964            let vals = eval_args()?;
965            if vals.len() != 1 {
966                return Err(CypherError::InvalidQuery(
967                    "toLower() takes exactly 1 argument".into(),
968                ));
969            }
970            match &vals[0] {
971                CypherValue::String(s) => Ok(CypherValue::String(s.to_lowercase())),
972                CypherValue::Null => Ok(CypherValue::Null),
973                _ => Err(CypherError::TypeMismatch(format!(
974                    "toLower() requires a string, got {:?}",
975                    vals[0]
976                ))),
977            }
978        }
979        "trim" => {
980            let vals = eval_args()?;
981            if vals.len() != 1 {
982                return Err(CypherError::InvalidQuery(
983                    "trim() takes exactly 1 argument".into(),
984                ));
985            }
986            match &vals[0] {
987                CypherValue::String(s) => Ok(CypherValue::String(s.trim().to_string())),
988                CypherValue::Null => Ok(CypherValue::Null),
989                _ => Err(CypherError::TypeMismatch(format!(
990                    "trim() requires a string, got {:?}",
991                    vals[0]
992                ))),
993            }
994        }
995        "ltrim" => {
996            let vals = eval_args()?;
997            if vals.len() != 1 {
998                return Err(CypherError::InvalidQuery(
999                    "ltrim() takes exactly 1 argument".into(),
1000                ));
1001            }
1002            match &vals[0] {
1003                CypherValue::String(s) => Ok(CypherValue::String(s.trim_start().to_string())),
1004                CypherValue::Null => Ok(CypherValue::Null),
1005                _ => Err(CypherError::TypeMismatch(format!(
1006                    "ltrim() requires a string, got {:?}",
1007                    vals[0]
1008                ))),
1009            }
1010        }
1011        "rtrim" => {
1012            let vals = eval_args()?;
1013            if vals.len() != 1 {
1014                return Err(CypherError::InvalidQuery(
1015                    "rtrim() takes exactly 1 argument".into(),
1016                ));
1017            }
1018            match &vals[0] {
1019                CypherValue::String(s) => Ok(CypherValue::String(s.trim_end().to_string())),
1020                CypherValue::Null => Ok(CypherValue::Null),
1021                _ => Err(CypherError::TypeMismatch(format!(
1022                    "rtrim() requires a string, got {:?}",
1023                    vals[0]
1024                ))),
1025            }
1026        }
1027        "substring" => {
1028            let vals = eval_args()?;
1029            if vals.len() < 2 || vals.len() > 3 {
1030                return Err(CypherError::InvalidQuery(
1031                    "substring() takes 2 or 3 arguments".into(),
1032                ));
1033            }
1034            match (&vals[0], &vals[1]) {
1035                (CypherValue::String(s), CypherValue::Integer(start)) => {
1036                    let start = *start as usize;
1037                    let len = vals.get(2).and_then(|v| match v {
1038                        CypherValue::Integer(i) => Some(*i as usize),
1039                        _ => None,
1040                    });
1041                    let result = if let Some(len) = len {
1042                        s.chars().skip(start).take(len).collect()
1043                    } else {
1044                        s.chars().skip(start).collect()
1045                    };
1046                    Ok(CypherValue::String(result))
1047                }
1048                (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
1049                _ => Err(CypherError::TypeMismatch(format!(
1050                    "substring() requires (string, integer[, integer]), got {:?}",
1051                    vals
1052                ))),
1053            }
1054        }
1055        "replace" => {
1056            let vals = eval_args()?;
1057            if vals.len() != 3 {
1058                return Err(CypherError::InvalidQuery(
1059                    "replace() takes exactly 3 arguments".into(),
1060                ));
1061            }
1062            match (&vals[0], &vals[1], &vals[2]) {
1063                (CypherValue::String(s), CypherValue::String(old), CypherValue::String(new)) => {
1064                    Ok(CypherValue::String(s.replace(old, new)))
1065                }
1066                (CypherValue::Null, _, _)
1067                | (_, CypherValue::Null, _)
1068                | (_, _, CypherValue::Null) => Ok(CypherValue::Null),
1069                _ => Err(CypherError::TypeMismatch(format!(
1070                    "replace() requires (string, string, string), got {:?}",
1071                    vals
1072                ))),
1073            }
1074        }
1075        "split" => {
1076            let vals = eval_args()?;
1077            if vals.len() != 2 {
1078                return Err(CypherError::InvalidQuery(
1079                    "split() takes exactly 2 arguments".into(),
1080                ));
1081            }
1082            match (&vals[0], &vals[1]) {
1083                (CypherValue::String(s), CypherValue::String(delim)) => {
1084                    let parts: Vec<CypherValue> = s
1085                        .split(delim)
1086                        .map(|p| CypherValue::String(p.to_string()))
1087                        .collect();
1088                    Ok(CypherValue::List(parts))
1089                }
1090                (CypherValue::Null, _) | (_, CypherValue::Null) => Ok(CypherValue::Null),
1091                _ => Err(CypherError::TypeMismatch(format!(
1092                    "split() requires (string, string), got {:?}",
1093                    vals
1094                ))),
1095            }
1096        }
1097        "reverse" => {
1098            let vals = eval_args()?;
1099            if vals.len() != 1 {
1100                return Err(CypherError::InvalidQuery(
1101                    "reverse() takes exactly 1 argument".into(),
1102                ));
1103            }
1104            match &vals[0] {
1105                CypherValue::String(s) => Ok(CypherValue::String(s.chars().rev().collect())),
1106                CypherValue::List(list) => {
1107                    let mut reversed = list.clone();
1108                    reversed.reverse();
1109                    Ok(CypherValue::List(reversed))
1110                }
1111                CypherValue::Null => Ok(CypherValue::Null),
1112                _ => Err(CypherError::TypeMismatch(format!(
1113                    "reverse() requires a string or list, got {:?}",
1114                    vals[0]
1115                ))),
1116            }
1117        }
1118        "abs" => {
1119            let vals = eval_args()?;
1120            if vals.len() != 1 {
1121                return Err(CypherError::InvalidQuery(
1122                    "abs() takes exactly 1 argument".into(),
1123                ));
1124            }
1125            match &vals[0] {
1126                CypherValue::Integer(i) => Ok(CypherValue::Integer(i.abs())),
1127                CypherValue::Float(f) => Ok(CypherValue::Float(f.abs())),
1128                CypherValue::Null => Ok(CypherValue::Null),
1129                _ => Err(CypherError::TypeMismatch(format!(
1130                    "abs() requires a number, got {:?}",
1131                    vals[0]
1132                ))),
1133            }
1134        }
1135        "ceil" => {
1136            let vals = eval_args()?;
1137            if vals.len() != 1 {
1138                return Err(CypherError::InvalidQuery(
1139                    "ceil() takes exactly 1 argument".into(),
1140                ));
1141            }
1142            match &vals[0] {
1143                CypherValue::Float(f) => Ok(CypherValue::Float(f.ceil())),
1144                CypherValue::Integer(i) => Ok(CypherValue::Float(*i as f64)),
1145                CypherValue::Null => Ok(CypherValue::Null),
1146                _ => Err(CypherError::TypeMismatch(format!(
1147                    "ceil() requires a number, got {:?}",
1148                    vals[0]
1149                ))),
1150            }
1151        }
1152        "floor" => {
1153            let vals = eval_args()?;
1154            if vals.len() != 1 {
1155                return Err(CypherError::InvalidQuery(
1156                    "floor() takes exactly 1 argument".into(),
1157                ));
1158            }
1159            match &vals[0] {
1160                CypherValue::Float(f) => Ok(CypherValue::Float(f.floor())),
1161                CypherValue::Integer(i) => Ok(CypherValue::Float(*i as f64)),
1162                CypherValue::Null => Ok(CypherValue::Null),
1163                _ => Err(CypherError::TypeMismatch(format!(
1164                    "floor() requires a number, got {:?}",
1165                    vals[0]
1166                ))),
1167            }
1168        }
1169        "round" => {
1170            let vals = eval_args()?;
1171            if vals.len() != 1 {
1172                return Err(CypherError::InvalidQuery(
1173                    "round() takes exactly 1 argument".into(),
1174                ));
1175            }
1176            match &vals[0] {
1177                CypherValue::Float(f) => Ok(CypherValue::Float(f.round())),
1178                CypherValue::Integer(i) => Ok(CypherValue::Float(*i as f64)),
1179                CypherValue::Null => Ok(CypherValue::Null),
1180                _ => Err(CypherError::TypeMismatch(format!(
1181                    "round() requires a number, got {:?}",
1182                    vals[0]
1183                ))),
1184            }
1185        }
1186        "sign" => {
1187            let vals = eval_args()?;
1188            if vals.len() != 1 {
1189                return Err(CypherError::InvalidQuery(
1190                    "sign() takes exactly 1 argument".into(),
1191                ));
1192            }
1193            match &vals[0] {
1194                CypherValue::Integer(i) => Ok(CypherValue::Integer(i.signum())),
1195                CypherValue::Float(f) => Ok(CypherValue::Integer(f.signum() as i64)),
1196                CypherValue::Null => Ok(CypherValue::Null),
1197                _ => Err(CypherError::TypeMismatch(format!(
1198                    "sign() requires a number, got {:?}",
1199                    vals[0]
1200                ))),
1201            }
1202        }
1203        "sqrt" => {
1204            let vals = eval_args()?;
1205            if vals.len() != 1 {
1206                return Err(CypherError::InvalidQuery(
1207                    "sqrt() takes exactly 1 argument".into(),
1208                ));
1209            }
1210            match &vals[0] {
1211                CypherValue::Float(f) => Ok(CypherValue::Float(f.sqrt())),
1212                CypherValue::Integer(i) => Ok(CypherValue::Float((*i as f64).sqrt())),
1213                CypherValue::Null => Ok(CypherValue::Null),
1214                _ => Err(CypherError::TypeMismatch(format!(
1215                    "sqrt() requires a number, got {:?}",
1216                    vals[0]
1217                ))),
1218            }
1219        }
1220        "rand" => {
1221            if !args.is_empty() {
1222                return Err(CypherError::InvalidQuery(
1223                    "rand() takes no arguments".into(),
1224                ));
1225            }
1226            #[cfg(feature = "rng-rand")]
1227            {
1228                Ok(CypherValue::Float(rand::random::<f64>()))
1229            }
1230            #[cfg(all(feature = "rng-fastrand", not(feature = "rng-rand")))]
1231            {
1232                Ok(CypherValue::Float(fastrand::f64()))
1233            }
1234            #[cfg(not(any(feature = "rng-rand", feature = "rng-fastrand")))]
1235            {
1236                Err(CypherError::Unsupported(
1237                    "rand() requires an RNG feature to be enabled (rng-rand or rng-fastrand)"
1238                        .into(),
1239                ))
1240            }
1241        }
1242        "range" => {
1243            let vals = eval_args()?;
1244            if vals.len() < 2 || vals.len() > 3 {
1245                return Err(CypherError::InvalidQuery(
1246                    "range() takes 2 or 3 arguments".into(),
1247                ));
1248            }
1249            let start = match &vals[0] {
1250                CypherValue::Integer(i) => *i,
1251                _ => {
1252                    return Err(CypherError::TypeMismatch(format!(
1253                        "range() requires integers, got {:?}",
1254                        vals
1255                    )));
1256                }
1257            };
1258            let end = match &vals[1] {
1259                CypherValue::Integer(i) => *i,
1260                _ => {
1261                    return Err(CypherError::TypeMismatch(format!(
1262                        "range() requires integers, got {:?}",
1263                        vals
1264                    )));
1265                }
1266            };
1267            let step = match vals.get(2) {
1268                Some(v) => match v {
1269                    CypherValue::Integer(i) => *i,
1270                    _ => {
1271                        return Err(CypherError::TypeMismatch(
1272                            "range() step must be an integer".into(),
1273                        ));
1274                    }
1275                },
1276                None => 1,
1277            };
1278            if step == 0 {
1279                return Err(CypherError::InvalidQuery(
1280                    "range() step cannot be zero".into(),
1281                ));
1282            }
1283            let mut result = Vec::new();
1284            let mut current = start;
1285            if step > 0 {
1286                while current <= end {
1287                    result.push(CypherValue::Integer(current));
1288                    current += step;
1289                }
1290            } else {
1291                while current >= end {
1292                    result.push(CypherValue::Integer(current));
1293                    current += step;
1294                }
1295            }
1296            Ok(CypherValue::List(result))
1297        }
1298        "tail" => {
1299            let vals = eval_args()?;
1300            if vals.len() != 1 {
1301                return Err(CypherError::InvalidQuery(
1302                    "tail() takes exactly 1 argument".into(),
1303                ));
1304            }
1305            match &vals[0] {
1306                CypherValue::List(list) => {
1307                    Ok(CypherValue::List(list.iter().skip(1).cloned().collect()))
1308                }
1309                CypherValue::Null => Ok(CypherValue::Null),
1310                _ => Err(CypherError::TypeMismatch(format!(
1311                    "tail() requires a list, got {:?}",
1312                    vals[0]
1313                ))),
1314            }
1315        }
1316        "type" => {
1317            if args.len() != 1 {
1318                return Err(CypherError::InvalidQuery(
1319                    "type() takes exactly 1 argument".into(),
1320                ));
1321            }
1322            match &args[0] {
1323                Expression::Variable(var) => {
1324                    if let Some(BoundValue::Edge(idx)) = bindings.get(var) {
1325                        let data = &graph[*idx];
1326                        Ok(data
1327                            .rel_type()
1328                            .map(|s| CypherValue::String(s.to_string()))
1329                            .unwrap_or(CypherValue::Null))
1330                    } else {
1331                        Ok(CypherValue::Null)
1332                    }
1333                }
1334                _ => Err(CypherError::TypeMismatch(
1335                    "type() requires a relationship variable".into(),
1336                )),
1337            }
1338        }
1339        "labels" => {
1340            if args.len() != 1 {
1341                return Err(CypherError::InvalidQuery(
1342                    "labels() takes exactly 1 argument".into(),
1343                ));
1344            }
1345            match &args[0] {
1346                Expression::Variable(var) => {
1347                    if let Some(BoundValue::Node(idx)) = bindings.get(var) {
1348                        let data = &graph[*idx];
1349                        let labels: Vec<CypherValue> =
1350                            data.labels().into_iter().map(CypherValue::String).collect();
1351                        Ok(CypherValue::List(labels))
1352                    } else {
1353                        Ok(CypherValue::Null)
1354                    }
1355                }
1356                _ => Err(CypherError::TypeMismatch(
1357                    "labels() requires a node variable".into(),
1358                )),
1359            }
1360        }
1361        "properties" => {
1362            if args.len() != 1 {
1363                return Err(CypherError::InvalidQuery(
1364                    "properties() takes exactly 1 argument".into(),
1365                ));
1366            }
1367            match &args[0] {
1368                Expression::Variable(var) => {
1369                    if let Some(bound) = bindings.get(var) {
1370                        let props = match bound {
1371                            BoundValue::Node(idx) => graph[*idx].properties(),
1372                            BoundValue::Edge(idx) => graph[*idx].properties(),
1373                        };
1374                        Ok(CypherValue::Map(props))
1375                    } else {
1376                        Ok(CypherValue::Null)
1377                    }
1378                }
1379                _ => Err(CypherError::TypeMismatch(
1380                    "properties() requires a variable".into(),
1381                )),
1382            }
1383        }
1384        "keys" => {
1385            if args.len() != 1 {
1386                return Err(CypherError::InvalidQuery(
1387                    "keys() takes exactly 1 argument".into(),
1388                ));
1389            }
1390            match &args[0] {
1391                Expression::Variable(var) => {
1392                    if let Some(bound) = bindings.get(var) {
1393                        let props = match bound {
1394                            BoundValue::Node(idx) => graph[*idx].properties(),
1395                            BoundValue::Edge(idx) => graph[*idx].properties(),
1396                        };
1397                        let keys: Vec<CypherValue> =
1398                            props.keys().cloned().map(CypherValue::String).collect();
1399                        Ok(CypherValue::List(keys))
1400                    } else {
1401                        Ok(CypherValue::Null)
1402                    }
1403                }
1404                _ => Err(CypherError::TypeMismatch(
1405                    "keys() requires a variable".into(),
1406                )),
1407            }
1408        }
1409        "exists" => {
1410            if args.len() != 1 {
1411                return Err(CypherError::InvalidQuery(
1412                    "exists() takes exactly 1 argument".into(),
1413                ));
1414            }
1415            match &args[0] {
1416                Expression::Property(var, prop) => {
1417                    let exists = if let Some(bound) = bindings.get(var) {
1418                        match bound {
1419                            BoundValue::Node(idx) => graph[*idx].get(prop).is_some(),
1420                            BoundValue::Edge(idx) => graph[*idx].get(prop).is_some(),
1421                        }
1422                    } else {
1423                        false
1424                    };
1425                    Ok(CypherValue::Boolean(exists))
1426                }
1427                _ => {
1428                    // For general expressions, check if not null
1429                    let val = evaluate_expression(&args[0], bindings, graph, parameters)?;
1430                    Ok(CypherValue::Boolean(!matches!(val, CypherValue::Null)))
1431                }
1432            }
1433        }
1434        _ => Err(CypherError::Unsupported(format!(
1435            "unsupported function: {}",
1436            name
1437        ))),
1438    }
1439}
1440
1441fn evaluate_case(
1442    case: &CaseExpr,
1443    bindings: &Bindings,
1444    graph: &Graph<impl CypherNode, impl CypherEdge>,
1445    parameters: &Parameters,
1446) -> Result<CypherValue, CypherError> {
1447    if let Some(scrutinee) = &case.scrutinee {
1448        let scrutinee_val = evaluate_expression(scrutinee, bindings, graph, parameters)?;
1449        for (when, then) in &case.alternatives {
1450            let when_val = evaluate_expression(when, bindings, graph, parameters)?;
1451            if scrutinee_val == when_val {
1452                return evaluate_expression(then, bindings, graph, parameters);
1453            }
1454        }
1455    } else {
1456        for (when, then) in &case.alternatives {
1457            let when_val = evaluate_expression(when, bindings, graph, parameters)?;
1458            if is_truthy(&when_val) == Some(true) {
1459                return evaluate_expression(then, bindings, graph, parameters);
1460            }
1461        }
1462    }
1463    if let Some(default) = &case.default {
1464        evaluate_expression(default, bindings, graph, parameters)
1465    } else {
1466        Ok(CypherValue::Null)
1467    }
1468}
1469
1470fn evaluate_expression(
1471    expr: &Expression,
1472    bindings: &Bindings,
1473    graph: &Graph<impl CypherNode, impl CypherEdge>,
1474    parameters: &Parameters,
1475) -> Result<CypherValue, CypherError> {
1476    match expr {
1477        Expression::Variable(var) => {
1478            if let Some(bound) = bindings.get(var) {
1479                match bound {
1480                    BoundValue::Node(_) | BoundValue::Edge(_) => {
1481                        Err(CypherError::TypeMismatch(format!(
1482                            "node/edge variable '{}' cannot be used in a scalar expression",
1483                            var
1484                        )))
1485                    }
1486                }
1487            } else {
1488                Ok(CypherValue::Null)
1489            }
1490        }
1491        Expression::Property(var, prop) => {
1492            if let Some(bound) = bindings.get(var) {
1493                match bound {
1494                    BoundValue::Node(idx) => {
1495                        let data = &graph[*idx];
1496                        Ok(data.get(prop).cloned().unwrap_or(CypherValue::Null))
1497                    }
1498                    BoundValue::Edge(idx) => {
1499                        let data = &graph[*idx];
1500                        Ok(data.get(prop).cloned().unwrap_or(CypherValue::Null))
1501                    }
1502                }
1503            } else {
1504                Ok(CypherValue::Null)
1505            }
1506        }
1507        Expression::Literal(v) => Ok(v.clone()),
1508        Expression::All => Ok(CypherValue::Null),
1509        Expression::Unary(op, expr) => {
1510            let val = evaluate_expression(expr, bindings, graph, parameters)?;
1511            match op {
1512                UnaryOp::Not => match val {
1513                    CypherValue::Null => Ok(CypherValue::Null),
1514                    _ => Ok(CypherValue::Boolean(is_truthy(&val) == Some(false))),
1515                },
1516                UnaryOp::Negate => match val {
1517                    CypherValue::Integer(i) => Ok(CypherValue::Integer(-i)),
1518                    CypherValue::Float(f) => Ok(CypherValue::Float(-f)),
1519                    CypherValue::Null => Ok(CypherValue::Null),
1520                    _ => Err(CypherError::TypeMismatch(format!(
1521                        "cannot negate {:?}",
1522                        val
1523                    ))),
1524                },
1525                UnaryOp::Plus => match val {
1526                    CypherValue::Integer(_) | CypherValue::Float(_) | CypherValue::Null => Ok(val),
1527                    _ => Err(CypherError::TypeMismatch(format!(
1528                        "cannot apply + to {:?}",
1529                        val
1530                    ))),
1531                },
1532            }
1533        }
1534        Expression::Binary(op, left, right) => {
1535            let left_val = evaluate_expression(left, bindings, graph, parameters)?;
1536            let right_val = evaluate_expression(right, bindings, graph, parameters)?;
1537            evaluate_binary_op(*op, left_val, right_val)
1538        }
1539        Expression::List(items) => {
1540            let values = items
1541                .iter()
1542                .map(|item| evaluate_expression(item, bindings, graph, parameters))
1543                .collect::<Result<Vec<_>, _>>()?;
1544            Ok(CypherValue::List(values))
1545        }
1546        Expression::FunctionCall {
1547            name,
1548            args,
1549            distinct,
1550        } => evaluate_function(name, args, *distinct, bindings, graph, parameters),
1551        Expression::Parameter(name) => parameters
1552            .get(name)
1553            .cloned()
1554            .ok_or_else(|| CypherError::InvalidQuery(format!("missing query parameter: ${name}"))),
1555        Expression::Case(case) => evaluate_case(case, bindings, graph, parameters),
1556    }
1557}
1558
1559fn evaluate_where(
1560    expr: &WhereExpr,
1561    bindings: &Bindings,
1562    graph: &Graph<impl CypherNode, impl CypherEdge>,
1563    parameters: &Parameters,
1564) -> bool {
1565    match expr {
1566        WhereExpr::Eq(left, right) => {
1567            match (
1568                evaluate_expression(left, bindings, graph, parameters),
1569                evaluate_expression(right, bindings, graph, parameters),
1570            ) {
1571                (Ok(l), Ok(r)) => matches!(cypher_value_eq(&l, &r), CypherValue::Boolean(true)),
1572                _ => false,
1573            }
1574        }
1575        WhereExpr::NotEq(left, right) => {
1576            match (
1577                evaluate_expression(left, bindings, graph, parameters),
1578                evaluate_expression(right, bindings, graph, parameters),
1579            ) {
1580                (Ok(l), Ok(r)) => matches!(cypher_value_eq(&l, &r), CypherValue::Boolean(false)),
1581                _ => false,
1582            }
1583        }
1584        WhereExpr::Lt(left, right) => {
1585            match (
1586                evaluate_expression(left, bindings, graph, parameters),
1587                evaluate_expression(right, bindings, graph, parameters),
1588            ) {
1589                (Ok(l), Ok(r)) => compare_cypher_values(&l, &r) == Some(std::cmp::Ordering::Less),
1590                _ => false,
1591            }
1592        }
1593        WhereExpr::Gt(left, right) => {
1594            match (
1595                evaluate_expression(left, bindings, graph, parameters),
1596                evaluate_expression(right, bindings, graph, parameters),
1597            ) {
1598                (Ok(l), Ok(r)) => {
1599                    compare_cypher_values(&l, &r) == Some(std::cmp::Ordering::Greater)
1600                }
1601                _ => false,
1602            }
1603        }
1604        WhereExpr::Le(left, right) => {
1605            match (
1606                evaluate_expression(left, bindings, graph, parameters),
1607                evaluate_expression(right, bindings, graph, parameters),
1608            ) {
1609                (Ok(l), Ok(r)) => {
1610                    matches!(
1611                        compare_cypher_values(&l, &r),
1612                        Some(std::cmp::Ordering::Less | std::cmp::Ordering::Equal)
1613                    )
1614                }
1615                _ => false,
1616            }
1617        }
1618        WhereExpr::Ge(left, right) => {
1619            match (
1620                evaluate_expression(left, bindings, graph, parameters),
1621                evaluate_expression(right, bindings, graph, parameters),
1622            ) {
1623                (Ok(l), Ok(r)) => {
1624                    matches!(
1625                        compare_cypher_values(&l, &r),
1626                        Some(std::cmp::Ordering::Greater | std::cmp::Ordering::Equal)
1627                    )
1628                }
1629                _ => false,
1630            }
1631        }
1632        WhereExpr::In(expression, values) => {
1633            match evaluate_expression(expression, bindings, graph, parameters) {
1634                Ok(val) => values.iter().any(|v| v == &val),
1635                _ => false,
1636            }
1637        }
1638        WhereExpr::StartsWith(expression, prefix) => {
1639            match evaluate_expression(expression, bindings, graph, parameters) {
1640                Ok(CypherValue::String(s)) => s.starts_with(prefix),
1641                _ => false,
1642            }
1643        }
1644        WhereExpr::EndsWith(expression, suffix) => {
1645            match evaluate_expression(expression, bindings, graph, parameters) {
1646                Ok(CypherValue::String(s)) => s.ends_with(suffix),
1647                _ => false,
1648            }
1649        }
1650        WhereExpr::Contains(expression, substring) => {
1651            match evaluate_expression(expression, bindings, graph, parameters) {
1652                Ok(CypherValue::String(s)) => s.contains(substring),
1653                _ => false,
1654            }
1655        }
1656        WhereExpr::IsNull(expression) => {
1657            matches!(
1658                evaluate_expression(expression, bindings, graph, parameters),
1659                Ok(CypherValue::Null)
1660            )
1661        }
1662        WhereExpr::IsNotNull(expression) => !matches!(
1663            evaluate_expression(expression, bindings, graph, parameters),
1664            Ok(CypherValue::Null)
1665        ),
1666        WhereExpr::Not(inner) => !evaluate_where(inner, bindings, graph, parameters),
1667        WhereExpr::And(left, right) => {
1668            evaluate_where(left, bindings, graph, parameters)
1669                && evaluate_where(right, bindings, graph, parameters)
1670        }
1671        WhereExpr::Or(left, right) => {
1672            evaluate_where(left, bindings, graph, parameters)
1673                || evaluate_where(right, bindings, graph, parameters)
1674        }
1675        WhereExpr::Xor(left, right) => {
1676            evaluate_where(left, bindings, graph, parameters)
1677                ^ evaluate_where(right, bindings, graph, parameters)
1678        }
1679    }
1680}
1681
1682// ---------------------------------------------------------------------------
1683// Pattern matching
1684// ---------------------------------------------------------------------------
1685
1686/// Shared matcher for pattern matching against a graph. Used by both the read
1687/// and mutation executors to avoid code duplication.
1688struct PatternMatcher<'a, N: CypherNode, E: CypherEdge> {
1689    graph: &'a Graph<N, E>,
1690    strategy: MatchStrategy,
1691}
1692
1693impl<'a, N: CypherNode, E: CypherEdge> PatternMatcher<'a, N, E> {
1694    fn new(graph: &'a Graph<N, E>, strategy: MatchStrategy) -> Self {
1695        Self { graph, strategy }
1696    }
1697
1698    fn match_patterns(&self, patterns: &[PathPattern], base_bindings: &Bindings) -> Vec<Bindings> {
1699        if patterns.is_empty() {
1700            return vec![base_bindings.clone()];
1701        }
1702
1703        let per_pattern: Vec<Vec<Bindings>> = patterns
1704            .iter()
1705            .map(|p| self.match_single_path(p, base_bindings))
1706            .collect();
1707
1708        per_pattern
1709            .iter()
1710            .multi_cartesian_product()
1711            .filter_map(|combo| {
1712                let mut merged = base_bindings.clone();
1713                for binding_set in combo {
1714                    for (k, v) in binding_set {
1715                        if let Some(existing) = merged.get(k) {
1716                            if existing != v {
1717                                return None;
1718                            }
1719                        } else {
1720                            merged.insert(k.clone(), *v);
1721                        }
1722                    }
1723                }
1724                Some(merged)
1725            })
1726            .collect()
1727    }
1728
1729    fn match_single_path(&self, pattern: &PathPattern, base_bindings: &Bindings) -> Vec<Bindings> {
1730        match self.strategy {
1731            MatchStrategy::Backtrack => self.match_path_backtrack(pattern, base_bindings),
1732            MatchStrategy::Fast => self.match_path_fast(pattern, base_bindings),
1733        }
1734    }
1735
1736    fn match_path_backtrack(
1737        &self,
1738        pattern: &PathPattern,
1739        base_bindings: &Bindings,
1740    ) -> Vec<Bindings> {
1741        let mut results = Vec::new();
1742        let start_candidates = self.find_matching_nodes(&pattern.start, base_bindings);
1743
1744        for &start_node in &start_candidates {
1745            let mut bindings = base_bindings.clone();
1746            if let Some(var) = &pattern.start.variable {
1747                bindings.insert(var.clone(), BoundValue::Node(start_node));
1748            }
1749            self.match_path_hops(pattern, 0, start_node, &mut bindings, &mut results);
1750        }
1751
1752        results
1753    }
1754
1755    fn match_path_hops(
1756        &self,
1757        pattern: &PathPattern,
1758        hop_index: usize,
1759        current_node: NodeIndex,
1760        bindings: &mut Bindings,
1761        results: &mut Vec<Bindings>,
1762    ) {
1763        if hop_index >= pattern.rels.len() {
1764            results.push(bindings.clone());
1765            return;
1766        }
1767
1768        let (rel, target_node) = &pattern.rels[hop_index];
1769
1770        let candidate_edges: Vec<_> = match rel.direction {
1771            RelDirection::Right => self
1772                .graph
1773                .edges_directed(current_node, petgraph::Direction::Outgoing)
1774                .filter(|e| Self::edge_matches_rel(e, rel))
1775                .map(|e| (e, e.target()))
1776                .collect(),
1777            RelDirection::Left => self
1778                .graph
1779                .edges_directed(current_node, petgraph::Direction::Incoming)
1780                .filter(|e| Self::edge_matches_rel(e, rel))
1781                .map(|e| (e, e.source()))
1782                .collect(),
1783            RelDirection::Both => {
1784                let out = self
1785                    .graph
1786                    .edges_directed(current_node, petgraph::Direction::Outgoing)
1787                    .filter(|e| Self::edge_matches_rel(e, rel))
1788                    .map(|e| (e, e.target()));
1789                let incoming = self
1790                    .graph
1791                    .edges_directed(current_node, petgraph::Direction::Incoming)
1792                    .filter(|e| Self::edge_matches_rel(e, rel))
1793                    .map(|e| (e, e.source()));
1794                out.chain(incoming).collect()
1795            }
1796        };
1797
1798        for (edge_ref, actual_target) in candidate_edges {
1799            if self.node_matches_pattern(actual_target, target_node) {
1800                let mut new_bindings = bindings.clone();
1801
1802                if let Some(var) = &rel.variable {
1803                    let edge_val = BoundValue::Edge(edge_ref.id());
1804                    if let Some(existing) = new_bindings.get(var) {
1805                        if existing != &edge_val {
1806                            continue;
1807                        }
1808                    } else {
1809                        new_bindings.insert(var.clone(), edge_val);
1810                    }
1811                }
1812                if let Some(var) = &target_node.variable {
1813                    let node_val = BoundValue::Node(actual_target);
1814                    if let Some(existing) = new_bindings.get(var) {
1815                        if existing != &node_val {
1816                            continue;
1817                        }
1818                    } else {
1819                        new_bindings.insert(var.clone(), node_val);
1820                    }
1821                }
1822
1823                self.match_path_hops(
1824                    pattern,
1825                    hop_index + 1,
1826                    actual_target,
1827                    &mut new_bindings,
1828                    results,
1829                );
1830            }
1831        }
1832    }
1833
1834    fn match_path_fast(&self, pattern: &PathPattern, base_bindings: &Bindings) -> Vec<Bindings> {
1835        self.match_path_backtrack(pattern, base_bindings)
1836    }
1837
1838    fn find_matching_nodes(
1839        &self,
1840        pattern: &NodePattern,
1841        base_bindings: &Bindings,
1842    ) -> Vec<NodeIndex> {
1843        if let Some(var) = &pattern.variable
1844            && let Some(&BoundValue::Node(idx)) = base_bindings.get(var)
1845        {
1846            if self.node_matches_pattern(idx, pattern) {
1847                return vec![idx];
1848            }
1849            return vec![];
1850        }
1851
1852        self.graph
1853            .node_indices()
1854            .filter(|&idx| self.node_matches_pattern(idx, pattern))
1855            .collect()
1856    }
1857
1858    fn node_matches_pattern(&self, node_idx: NodeIndex, pattern: &NodePattern) -> bool {
1859        let node_data = &self.graph[node_idx];
1860        if !pattern
1861            .labels
1862            .iter()
1863            .all(|label| node_data.has_label(label))
1864        {
1865            return false;
1866        }
1867        for (key, value) in &pattern.properties {
1868            if node_data.get(key) != Some(value) {
1869                return false;
1870            }
1871        }
1872        true
1873    }
1874
1875    fn edge_matches_rel(edge_ref: &EdgeReference<'_, E>, rel: &RelPattern) -> bool {
1876        let edge_data = edge_ref.weight();
1877        if let Some(ref rel_type) = rel.rel_type
1878            && !edge_data.has_rel_type(rel_type)
1879        {
1880            return false;
1881        }
1882        for (key, value) in &rel.properties {
1883            if edge_data.get(key) != Some(value) {
1884                return false;
1885            }
1886        }
1887        true
1888    }
1889}
1890
1891// ---------------------------------------------------------------------------
1892// Result projection helpers
1893// ---------------------------------------------------------------------------
1894
1895fn project_expression(
1896    expr: &Expression,
1897    bindings: &Bindings,
1898    graph: &Graph<impl CypherNode, impl CypherEdge>,
1899    parameters: &Parameters,
1900) -> Result<ResultValue, CypherError> {
1901    match expr {
1902        Expression::Variable(var) => {
1903            if let Some(&bound) = bindings.get(var) {
1904                match bound {
1905                    BoundValue::Node(idx) => {
1906                        let data = &graph[idx];
1907                        Ok(ResultValue::Node {
1908                            labels: data.labels(),
1909                            properties: data.properties(),
1910                        })
1911                    }
1912                    BoundValue::Edge(idx) => {
1913                        let data = &graph[idx];
1914                        Ok(ResultValue::Edge {
1915                            rel_type: data.rel_type().map(str::to_string),
1916                            properties: data.properties(),
1917                        })
1918                    }
1919                }
1920            } else {
1921                Ok(ResultValue::Scalar(CypherValue::Null))
1922            }
1923        }
1924        Expression::Property(var, prop) => {
1925            if let Some(&bound) = bindings.get(var) {
1926                match bound {
1927                    BoundValue::Node(idx) => {
1928                        let data = &graph[idx];
1929                        Ok(data
1930                            .get(prop)
1931                            .cloned()
1932                            .map(ResultValue::Scalar)
1933                            .unwrap_or(ResultValue::Scalar(CypherValue::Null)))
1934                    }
1935                    BoundValue::Edge(idx) => {
1936                        let data = &graph[idx];
1937                        Ok(data
1938                            .get(prop)
1939                            .cloned()
1940                            .map(ResultValue::Scalar)
1941                            .unwrap_or(ResultValue::Scalar(CypherValue::Null)))
1942                    }
1943                }
1944            } else {
1945                Ok(ResultValue::Scalar(CypherValue::Null))
1946            }
1947        }
1948        Expression::All => Ok(ResultValue::Scalar(CypherValue::Null)),
1949        other => {
1950            let val = evaluate_expression(other, bindings, graph, parameters)?;
1951            Ok(ResultValue::Scalar(val))
1952        }
1953    }
1954}
1955
1956fn column_name(item: &ReturnItem, expr_counter: &mut usize) -> Option<String> {
1957    if let Some(ref alias) = item.alias {
1958        Some(alias.clone())
1959    } else {
1960        match &item.expression {
1961            Expression::Variable(v) => Some(v.clone()),
1962            Expression::Property(v, p) => Some(format!("{}.{}", v, p)),
1963            Expression::All => None,
1964            _ => {
1965                let name = format!("expr{}", *expr_counter);
1966                *expr_counter += 1;
1967                Some(name)
1968            }
1969        }
1970    }
1971}
1972
1973fn result_columns(items: &[ReturnItem]) -> Vec<String> {
1974    let mut expr_counter = 0;
1975    items
1976        .iter()
1977        .filter_map(|item| column_name(item, &mut expr_counter))
1978        .collect()
1979}
1980
1981fn project_row(
1982    items: &[ReturnItem],
1983    bindings: &Bindings,
1984    graph: &Graph<impl CypherNode, impl CypherEdge>,
1985    parameters: &Parameters,
1986) -> Result<Row, CypherError> {
1987    let mut values = HashMap::new();
1988    let mut expr_counter = 0;
1989
1990    for item in items {
1991        let key = column_name(item, &mut expr_counter).unwrap_or_else(|| "*".to_string());
1992
1993        if matches!(item.expression, Expression::All) {
1994            for (var, bound) in bindings {
1995                match bound {
1996                    BoundValue::Node(idx) => {
1997                        let data = &graph[*idx];
1998                        values.insert(
1999                            var.clone(),
2000                            ResultValue::Node {
2001                                labels: data.labels(),
2002                                properties: data.properties(),
2003                            },
2004                        );
2005                    }
2006                    BoundValue::Edge(idx) => {
2007                        let data = &graph[*idx];
2008                        values.insert(
2009                            var.clone(),
2010                            ResultValue::Edge {
2011                                rel_type: data.rel_type().map(str::to_string),
2012                                properties: data.properties(),
2013                            },
2014                        );
2015                    }
2016                }
2017            }
2018            continue;
2019        }
2020
2021        let result_value = project_expression(&item.expression, bindings, graph, parameters)?;
2022        values.insert(key, result_value);
2023    }
2024
2025    Ok(Row { values })
2026}
2027
2028fn compare_sort_keys(a: &CypherValue, b: &CypherValue) -> std::cmp::Ordering {
2029    a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
2030}
2031
2032fn stable_cypher_value_string(v: &CypherValue) -> String {
2033    match v {
2034        CypherValue::Null => "N".to_string(),
2035        CypherValue::Boolean(b) => format!("B{}", b),
2036        CypherValue::Integer(i) => format!("I{}", i),
2037        CypherValue::Float(f) => format!("F{}", f.to_bits()),
2038        CypherValue::String(s) => format!("S{}", s),
2039        CypherValue::List(items) => {
2040            let parts: Vec<_> = items.iter().map(stable_cypher_value_string).collect();
2041            format!("L[{}]", parts.join(","))
2042        }
2043        CypherValue::Map(entries) => {
2044            let mut keys: Vec<_> = entries.keys().collect();
2045            keys.sort();
2046            let parts: Vec<_> = keys
2047                .iter()
2048                .map(|k| format!("{}:{}", k, stable_cypher_value_string(&entries[*k])))
2049                .collect();
2050            format!("M{{{}}}", parts.join(","))
2051        }
2052    }
2053}
2054
2055fn stable_result_value_string(v: &ResultValue) -> String {
2056    match v {
2057        ResultValue::Scalar(cv) => stable_cypher_value_string(cv),
2058        ResultValue::Node { labels, properties } => {
2059            let mut labels: Vec<_> = labels.clone();
2060            labels.sort();
2061            let mut keys: Vec<_> = properties.keys().collect();
2062            keys.sort();
2063            let parts: Vec<_> = keys
2064                .iter()
2065                .map(|k| format!("{}:{}", k, stable_cypher_value_string(&properties[*k])))
2066                .collect();
2067            format!("Node({}:{{{}}})", labels.join(":"), parts.join(","))
2068        }
2069        ResultValue::Edge {
2070            rel_type,
2071            properties,
2072        } => {
2073            let mut keys: Vec<_> = properties.keys().collect();
2074            keys.sort();
2075            let parts: Vec<_> = keys
2076                .iter()
2077                .map(|k| format!("{}:{}", k, stable_cypher_value_string(&properties[*k])))
2078                .collect();
2079            format!(
2080                "Edge({}:{{{}}})",
2081                rel_type.as_deref().unwrap_or(""),
2082                parts.join(",")
2083            )
2084        }
2085    }
2086}
2087
2088fn row_canonical_key(row: &Row) -> String {
2089    let mut pairs: Vec<_> = row
2090        .values
2091        .iter()
2092        .map(|(k, v)| (k.clone(), stable_result_value_string(v)))
2093        .collect();
2094    pairs.sort_by(|a, b| a.0.cmp(&b.0));
2095    let mut result = String::new();
2096    for (k, v) in pairs {
2097        result.push_str(&k);
2098        result.push('=');
2099        result.push_str(&v);
2100        result.push(';');
2101    }
2102    result
2103}
2104
2105fn apply_distinct(rows: Vec<Row>) -> Vec<Row> {
2106    let mut result = Vec::new();
2107    let mut seen = std::collections::HashSet::new();
2108    for row in rows {
2109        let key = row_canonical_key(&row);
2110        if seen.insert(key) {
2111            result.push(row);
2112        }
2113    }
2114    result
2115}
2116
2117fn apply_skip_limit(rows: Vec<Row>, skip: Option<usize>, limit: Option<usize>) -> Vec<Row> {
2118    let skip = skip.unwrap_or(0);
2119    let mut rows: Vec<Row> = rows.into_iter().skip(skip).collect();
2120    if let Some(limit) = limit {
2121        rows.truncate(limit);
2122    }
2123    rows
2124}
2125
2126// ---------------------------------------------------------------------------
2127// Read-only executor
2128// ---------------------------------------------------------------------------
2129
2130struct ReadQueryExecutor<'g, 'p, N: CypherNode, E: CypherEdge> {
2131    graph: &'g Graph<N, E>,
2132    strategy: MatchStrategy,
2133    parameters: &'p Parameters,
2134}
2135
2136impl<'g, 'p, N: CypherNode, E: CypherEdge> ReadQueryExecutor<'g, 'p, N, E> {
2137    fn new(graph: &'g Graph<N, E>, strategy: MatchStrategy, parameters: &'p Parameters) -> Self {
2138        Self {
2139            graph,
2140            strategy,
2141            parameters,
2142        }
2143    }
2144
2145    fn execute(self, query: CypherQuery) -> Result<QueryResult<'g, N, E>, CypherError> {
2146        let mut bindings: Vec<Bindings> = vec![HashMap::new()];
2147        let mut return_items: Option<Vec<ReturnItem>> = None;
2148        let mut return_distinct = false;
2149        let mut order_by: Option<Vec<SortItem>> = None;
2150        let mut skip: Option<usize> = None;
2151        let mut limit: Option<usize> = None;
2152
2153        for clause in query.clauses {
2154            match clause {
2155                Clause::Match {
2156                    patterns,
2157                    where_clause,
2158                } => {
2159                    bindings = self.execute_match(patterns, bindings);
2160                    if let Some(where_expr) = where_clause {
2161                        bindings.retain(|b| {
2162                            evaluate_where(&where_expr, b, self.graph, self.parameters)
2163                        });
2164                    }
2165                }
2166                Clause::OptionalMatch {
2167                    patterns,
2168                    where_clause,
2169                } => {
2170                    bindings = self.execute_optional_match(patterns, bindings);
2171                    if let Some(where_expr) = where_clause {
2172                        bindings.retain(|b| {
2173                            evaluate_where(&where_expr, b, self.graph, self.parameters)
2174                        });
2175                    }
2176                }
2177                Clause::Unwind {
2178                    expression,
2179                    variable,
2180                } => {
2181                    bindings = self.execute_unwind(expression, variable, bindings)?;
2182                }
2183                Clause::Return { items, distinct } => {
2184                    return_items = Some(items);
2185                    return_distinct = distinct;
2186                }
2187                Clause::OrderBy { items } => {
2188                    order_by = Some(items);
2189                }
2190                Clause::Skip { count } => {
2191                    skip = Some(count);
2192                }
2193                Clause::Limit { count } => {
2194                    limit = Some(count);
2195                }
2196                Clause::Create { .. }
2197                | Clause::Merge { .. }
2198                | Clause::Delete { .. }
2199                | Clause::Set { .. }
2200                | Clause::Remove { .. }
2201                | Clause::With { .. } => {
2202                    unreachable!("validated by validate_read_query")
2203                }
2204            }
2205        }
2206
2207        // Apply ORDER BY to bindings before projection, so sort expressions
2208        // can reference variables that may not appear in the RETURN clause.
2209        if let Some(sort_items) = &order_by {
2210            let mut sort_keys: Vec<Vec<CypherValue>> = Vec::with_capacity(bindings.len());
2211            for binding in &bindings {
2212                let mut keys = Vec::with_capacity(sort_items.len());
2213                for item in sort_items {
2214                    let val = evaluate_expression(
2215                        &item.expression,
2216                        binding,
2217                        self.graph,
2218                        self.parameters,
2219                    )?;
2220                    keys.push(val);
2221                }
2222                sort_keys.push(keys);
2223            }
2224            let mut indexed: Vec<_> = bindings.into_iter().zip(sort_keys).collect();
2225            indexed.sort_by(|(_, a_keys), (_, b_keys)| {
2226                for ((a_val, b_val), item) in
2227                    a_keys.iter().zip(b_keys.iter()).zip(sort_items.iter())
2228                {
2229                    let cmp = compare_sort_keys(a_val, b_val);
2230                    if cmp != std::cmp::Ordering::Equal {
2231                        return match item.direction {
2232                            SortDirection::Asc => cmp,
2233                            SortDirection::Desc => cmp.reverse(),
2234                        };
2235                    }
2236                }
2237                std::cmp::Ordering::Equal
2238            });
2239            bindings = indexed.into_iter().map(|(b, _)| b).collect();
2240        }
2241
2242        let mut rows = Vec::new();
2243        if let Some(items) = return_items {
2244            let columns = result_columns(&items);
2245            for binding in bindings {
2246                let row = project_row(&items, &binding, self.graph, self.parameters)?;
2247                rows.push(row);
2248            }
2249
2250            if return_distinct {
2251                rows = apply_distinct(rows);
2252            }
2253
2254            rows = apply_skip_limit(rows, skip, limit);
2255
2256            return Ok(QueryResult::new(columns, rows, self.graph));
2257        }
2258
2259        Ok(QueryResult::new(vec![], rows, self.graph))
2260    }
2261
2262    fn execute_match(
2263        &self,
2264        patterns: Vec<PathPattern>,
2265        input_bindings: Vec<Bindings>,
2266    ) -> Vec<Bindings> {
2267        if patterns.is_empty() {
2268            return input_bindings;
2269        }
2270
2271        let matcher = PatternMatcher::new(self.graph, self.strategy);
2272        let mut results = Vec::new();
2273        for existing_bindings in &input_bindings {
2274            let pattern_results = matcher.match_patterns(&patterns, existing_bindings);
2275            results.extend(pattern_results);
2276        }
2277        results
2278    }
2279
2280    fn execute_optional_match(
2281        &self,
2282        patterns: Vec<PathPattern>,
2283        input_bindings: Vec<Bindings>,
2284    ) -> Vec<Bindings> {
2285        if patterns.is_empty() {
2286            return input_bindings;
2287        }
2288
2289        let matcher = PatternMatcher::new(self.graph, self.strategy);
2290        let mut results = Vec::new();
2291        for existing_bindings in &input_bindings {
2292            let pattern_results = matcher.match_patterns(&patterns, existing_bindings);
2293            if pattern_results.is_empty() {
2294                // Missing bindings already behave like NULL in the evaluator/projector,
2295                // so we just propagate the existing bindings without additions.
2296                results.push(existing_bindings.clone());
2297            } else {
2298                results.extend(pattern_results);
2299            }
2300        }
2301        results
2302    }
2303
2304    fn execute_unwind(
2305        &self,
2306        _expression: Expression,
2307        _variable: String,
2308        _input_bindings: Vec<Bindings>,
2309    ) -> Result<Vec<Bindings>, CypherError> {
2310        Err(CypherError::Unsupported(
2311            "UNWIND is not yet fully supported".into(),
2312        ))
2313    }
2314}
2315
2316// ---------------------------------------------------------------------------
2317// Mutating executor
2318// ---------------------------------------------------------------------------
2319
2320struct MutQueryExecutor<'a> {
2321    graph: &'a mut Graph<NodeData, EdgeData>,
2322}
2323
2324impl<'a> MutQueryExecutor<'a> {
2325    fn new(graph: &'a mut Graph<NodeData, EdgeData>) -> Self {
2326        Self { graph }
2327    }
2328
2329    fn execute(mut self, query: CypherQuery) -> Result<(), CypherError> {
2330        let mut bindings: Vec<Bindings> = vec![HashMap::new()];
2331        let no_parameters = Parameters::new();
2332
2333        for clause in query.clauses {
2334            match clause {
2335                Clause::Match {
2336                    patterns,
2337                    where_clause,
2338                } => {
2339                    bindings = self.execute_match(patterns, bindings);
2340                    if let Some(where_expr) = where_clause {
2341                        bindings
2342                            .retain(|b| evaluate_where(&where_expr, b, self.graph, &no_parameters));
2343                    }
2344                }
2345                Clause::OptionalMatch {
2346                    patterns,
2347                    where_clause,
2348                } => {
2349                    bindings = self.execute_optional_match(patterns, bindings);
2350                    if let Some(where_expr) = where_clause {
2351                        bindings
2352                            .retain(|b| evaluate_where(&where_expr, b, self.graph, &no_parameters));
2353                    }
2354                }
2355                Clause::Create { patterns } => {
2356                    for bindings_row in &mut bindings {
2357                        for pattern in &patterns {
2358                            self.apply_path_pattern_mut(bindings_row, pattern)?;
2359                        }
2360                    }
2361                }
2362                Clause::Merge {
2363                    pattern,
2364                    on_create,
2365                    on_match,
2366                } => {
2367                    for bindings_row in &mut bindings {
2368                        let matcher = PatternMatcher::new(self.graph, MatchStrategy::Backtrack);
2369                        let test_results = matcher.match_single_path(&pattern, bindings_row);
2370                        if test_results.is_empty() {
2371                            self.apply_path_pattern_mut(bindings_row, &pattern)?;
2372                            self.apply_set_items(bindings_row, &on_create, &no_parameters)?;
2373                        } else {
2374                            // Propagate matched bindings into bindings_row
2375                            let first = &test_results[0];
2376                            for (k, v) in first {
2377                                if let Some(existing) = bindings_row.get(k) {
2378                                    if existing != v {
2379                                        return Err(CypherError::InvalidQuery(format!(
2380                                            "MERGE binding conflict on '{}': existing value differs from matched value",
2381                                            k
2382                                        )));
2383                                    }
2384                                } else {
2385                                    bindings_row.insert(k.clone(), *v);
2386                                }
2387                            }
2388                            self.apply_set_items(bindings_row, &on_match, &no_parameters)?;
2389                        }
2390                    }
2391                }
2392                Clause::Delete { variables, detach } => {
2393                    self.execute_delete(&variables, detach, &bindings)?;
2394                }
2395                Clause::Set { items } => {
2396                    for bindings_row in &bindings {
2397                        self.apply_set_items(bindings_row, &items, &no_parameters)?;
2398                    }
2399                }
2400                Clause::Remove { items } => {
2401                    for bindings_row in &bindings {
2402                        self.apply_remove_items(bindings_row, &items)?;
2403                    }
2404                }
2405                Clause::Return { .. } => {
2406                    unreachable!("validated by validate_mutation_query")
2407                }
2408                Clause::OrderBy { .. }
2409                | Clause::Skip { .. }
2410                | Clause::Limit { .. }
2411                | Clause::Unwind { .. }
2412                | Clause::With { .. } => {
2413                    return Err(CypherError::Unsupported(format!(
2414                        "clause {:?} is not supported in mutation queries",
2415                        std::mem::discriminant(&clause)
2416                    )));
2417                }
2418            }
2419        }
2420
2421        Ok(())
2422    }
2423
2424    fn execute_match(
2425        &self,
2426        patterns: Vec<PathPattern>,
2427        input_bindings: Vec<Bindings>,
2428    ) -> Vec<Bindings> {
2429        if patterns.is_empty() {
2430            return input_bindings;
2431        }
2432
2433        let matcher = PatternMatcher::new(self.graph, MatchStrategy::Backtrack);
2434        let mut results = Vec::new();
2435        for existing_bindings in &input_bindings {
2436            let pattern_results = matcher.match_patterns(&patterns, existing_bindings);
2437            results.extend(pattern_results);
2438        }
2439        results
2440    }
2441
2442    fn execute_optional_match(
2443        &self,
2444        patterns: Vec<PathPattern>,
2445        input_bindings: Vec<Bindings>,
2446    ) -> Vec<Bindings> {
2447        if patterns.is_empty() {
2448            return input_bindings;
2449        }
2450
2451        let matcher = PatternMatcher::new(self.graph, MatchStrategy::Backtrack);
2452        let mut results = Vec::new();
2453        for existing_bindings in &input_bindings {
2454            let pattern_results = matcher.match_patterns(&patterns, existing_bindings);
2455            if pattern_results.is_empty() {
2456                results.push(existing_bindings.clone());
2457            } else {
2458                results.extend(pattern_results);
2459            }
2460        }
2461        results
2462    }
2463
2464    fn execute_delete(
2465        &mut self,
2466        variables: &[String],
2467        detach: bool,
2468        bindings: &[Bindings],
2469    ) -> Result<(), CypherError> {
2470        for var in variables {
2471            let bound_values: Vec<BoundValue> = bindings
2472                .iter()
2473                .filter_map(|b| b.get(var).copied())
2474                .unique()
2475                .collect();
2476
2477            if bound_values.is_empty() {
2478                continue;
2479            }
2480
2481            let (nodes, edges): (Vec<_>, Vec<_>) = bound_values
2482                .into_iter()
2483                .partition(|b| matches!(b, BoundValue::Node(_)));
2484
2485            for edge_idx in edges {
2486                if let BoundValue::Edge(idx) = edge_idx {
2487                    self.graph.remove_edge(idx);
2488                }
2489            }
2490
2491            for node_idx in nodes {
2492                if let BoundValue::Node(idx) = node_idx {
2493                    let out_edges: Vec<_> = self
2494                        .graph
2495                        .edges_directed(idx, petgraph::Direction::Outgoing)
2496                        .map(|e| e.id())
2497                        .collect();
2498                    let in_edges: Vec<_> = self
2499                        .graph
2500                        .edges_directed(idx, petgraph::Direction::Incoming)
2501                        .map(|e| e.id())
2502                        .collect();
2503                    let has_incident_edges = !out_edges.is_empty() || !in_edges.is_empty();
2504
2505                    if !detach && has_incident_edges {
2506                        return Err(CypherError::InvalidQuery(format!(
2507                            "Cannot delete node '{}' because it has incident edges. Use DETACH DELETE.",
2508                            var
2509                        )));
2510                    }
2511
2512                    if detach {
2513                        for edge_id in out_edges.into_iter().chain(in_edges) {
2514                            self.graph.remove_edge(edge_id);
2515                        }
2516                    }
2517                    self.graph.remove_node(idx);
2518                }
2519            }
2520        }
2521        Ok(())
2522    }
2523
2524    fn apply_set_items(
2525        &mut self,
2526        bindings: &Bindings,
2527        items: &[SetItem],
2528        parameters: &Parameters,
2529    ) -> Result<(), CypherError> {
2530        for item in items {
2531            match item {
2532                SetItem::SetProperty {
2533                    variable,
2534                    property,
2535                    value,
2536                } => {
2537                    if let Some(bound) = bindings.get(variable) {
2538                        let val = evaluate_expression(value, bindings, self.graph, parameters)?;
2539                        match bound {
2540                            BoundValue::Node(idx) => {
2541                                if matches!(val, CypherValue::Null) {
2542                                    self.graph[*idx].properties.remove(property);
2543                                } else {
2544                                    self.graph[*idx].properties.insert(property.clone(), val);
2545                                }
2546                            }
2547                            BoundValue::Edge(idx) => {
2548                                if matches!(val, CypherValue::Null) {
2549                                    self.graph[*idx].properties.remove(property);
2550                                } else {
2551                                    self.graph[*idx].properties.insert(property.clone(), val);
2552                                }
2553                            }
2554                        }
2555                    }
2556                }
2557                SetItem::SetVariable {
2558                    variable,
2559                    properties,
2560                } => {
2561                    if let Some(bound) = bindings.get(variable) {
2562                        match bound {
2563                            BoundValue::Node(idx) => {
2564                                self.graph[*idx].properties = properties.clone();
2565                            }
2566                            BoundValue::Edge(idx) => {
2567                                self.graph[*idx].properties = properties.clone();
2568                            }
2569                        }
2570                    }
2571                }
2572                SetItem::SetLabels { variable, labels } => {
2573                    if let Some(BoundValue::Node(idx)) = bindings.get(variable) {
2574                        let node_data = &mut self.graph[*idx];
2575                        for label in labels {
2576                            if !node_data.has_label(label) {
2577                                node_data.labels.push(label.clone());
2578                            }
2579                        }
2580                    }
2581                }
2582                SetItem::MergeProperties {
2583                    variable,
2584                    properties,
2585                } => {
2586                    if let Some(bound) = bindings.get(variable) {
2587                        match bound {
2588                            BoundValue::Node(idx) => {
2589                                for (k, v) in properties {
2590                                    self.graph[*idx].properties.insert(k.clone(), v.clone());
2591                                }
2592                            }
2593                            BoundValue::Edge(idx) => {
2594                                for (k, v) in properties {
2595                                    self.graph[*idx].properties.insert(k.clone(), v.clone());
2596                                }
2597                            }
2598                        }
2599                    }
2600                }
2601            }
2602        }
2603        Ok(())
2604    }
2605
2606    fn apply_remove_items(
2607        &mut self,
2608        bindings: &Bindings,
2609        items: &[RemoveItem],
2610    ) -> Result<(), CypherError> {
2611        for item in items {
2612            match item {
2613                RemoveItem::RemoveProperty { variable, property } => {
2614                    if let Some(bound) = bindings.get(variable) {
2615                        match bound {
2616                            BoundValue::Node(idx) => {
2617                                self.graph[*idx].properties.remove(property);
2618                            }
2619                            BoundValue::Edge(idx) => {
2620                                self.graph[*idx].properties.remove(property);
2621                            }
2622                        }
2623                    }
2624                }
2625                RemoveItem::RemoveLabels { variable, labels } => {
2626                    if let Some(BoundValue::Node(idx)) = bindings.get(variable) {
2627                        let node_data = &mut self.graph[*idx];
2628                        node_data.labels.retain(|l| !labels.contains(l));
2629                    }
2630                }
2631            }
2632        }
2633        Ok(())
2634    }
2635
2636    fn apply_path_pattern_mut(
2637        &mut self,
2638        var_map: &mut Bindings,
2639        pattern: &PathPattern,
2640    ) -> Result<(), CypherError> {
2641        let mut prev_idx = self.get_or_add_node_mut(var_map, &pattern.start)?;
2642
2643        for (rel, target_node) in &pattern.rels {
2644            let target_idx = self.get_or_add_node_mut(var_map, target_node)?;
2645
2646            let edge_data = EdgeData::from_cypher(
2647                rel.variable.clone(),
2648                rel.rel_type.clone(),
2649                rel.properties.clone(),
2650            );
2651
2652            let edge_id = match rel.direction {
2653                RelDirection::Right => self.graph.add_edge(prev_idx, target_idx, edge_data),
2654                RelDirection::Left => self.graph.add_edge(target_idx, prev_idx, edge_data),
2655                RelDirection::Both => self.graph.add_edge(prev_idx, target_idx, edge_data),
2656            };
2657
2658            if let Some(var) = &rel.variable {
2659                let edge_val = BoundValue::Edge(edge_id);
2660                if let Some(existing) = var_map.get(var) {
2661                    if existing != &edge_val {
2662                        return Err(CypherError::InvalidQuery(format!(
2663                            "Variable '{}' is already bound to a different value",
2664                            var
2665                        )));
2666                    }
2667                } else {
2668                    var_map.insert(var.clone(), edge_val);
2669                }
2670            }
2671
2672            prev_idx = target_idx;
2673        }
2674        Ok(())
2675    }
2676
2677    fn get_or_add_node_mut(
2678        &mut self,
2679        var_map: &mut Bindings,
2680        pattern: &NodePattern,
2681    ) -> Result<NodeIndex, CypherError> {
2682        if let Some(var) = &pattern.variable
2683            && let Some(&existing) = var_map.get(var)
2684        {
2685            match existing {
2686                BoundValue::Node(idx) => return Ok(idx),
2687                BoundValue::Edge(_) => {
2688                    return Err(CypherError::InvalidQuery(format!(
2689                        "Variable '{}' is bound to an edge, cannot use as node",
2690                        var
2691                    )));
2692                }
2693            }
2694        }
2695
2696        let data = NodeData::from_cypher(
2697            pattern.variable.clone(),
2698            pattern.labels.clone(),
2699            pattern.properties.clone(),
2700        );
2701
2702        let idx = self.graph.add_node(data);
2703
2704        if let Some(var) = &pattern.variable {
2705            var_map.insert(var.clone(), BoundValue::Node(idx));
2706        }
2707
2708        Ok(idx)
2709    }
2710}