Skip to main content

petgraph_decypher/
ast.rs

1//! Abstract Syntax Tree types for OpenCypher queries.
2
3use std::collections::HashMap;
4
5/// A literal value in a Cypher property map or expression.
6#[derive(Debug, Clone, PartialEq)]
7pub enum CypherValue {
8    /// A UTF-8 string literal.
9    String(String),
10    /// A 64-bit signed integer literal.
11    Integer(i64),
12    /// A 64-bit floating-point literal.
13    Float(f64),
14    /// A boolean literal (`true` / `false`).
15    Boolean(bool),
16    /// The `null` literal.
17    Null,
18    /// A list of values.
19    List(Vec<CypherValue>),
20    /// A map of key-value pairs.
21    Map(HashMap<String, CypherValue>),
22}
23
24impl std::fmt::Display for CypherValue {
25    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26        match self {
27            CypherValue::String(s) => write!(f, "\"{}\"", s),
28            CypherValue::Integer(i) => write!(f, "{}", i),
29            CypherValue::Float(v) => write!(f, "{}", v),
30            CypherValue::Boolean(b) => write!(f, "{}", b),
31            CypherValue::Null => write!(f, "null"),
32            CypherValue::List(items) => {
33                write!(f, "[")?;
34                for (i, item) in items.iter().enumerate() {
35                    if i > 0 {
36                        write!(f, ", ")?;
37                    }
38                    write!(f, "{}", item)?;
39                }
40                write!(f, "]")
41            }
42            CypherValue::Map(entries) => {
43                write!(f, "{{")?;
44                let mut keys: Vec<_> = entries.keys().collect();
45                keys.sort();
46                for (i, k) in keys.iter().enumerate() {
47                    if i > 0 {
48                        write!(f, ", ")?;
49                    }
50                    write!(f, "{}: {}", k, entries.get(*k).unwrap())?;
51                }
52                write!(f, "}}")
53            }
54        }
55    }
56}
57
58impl std::cmp::PartialOrd for CypherValue {
59    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
60        use std::cmp::Ordering;
61        match (self, other) {
62            (CypherValue::Null, CypherValue::Null) => Some(Ordering::Equal),
63            (CypherValue::Null, _) => Some(Ordering::Less),
64            (_, CypherValue::Null) => Some(Ordering::Greater),
65            (CypherValue::Boolean(a), CypherValue::Boolean(b)) => a.partial_cmp(b),
66            (CypherValue::Integer(a), CypherValue::Integer(b)) => a.partial_cmp(b),
67            (CypherValue::Integer(a), CypherValue::Float(b)) => (*a as f64).partial_cmp(b),
68            (CypherValue::Float(a), CypherValue::Integer(b)) => a.partial_cmp(&(*b as f64)),
69            (CypherValue::Float(a), CypherValue::Float(b)) => a.partial_cmp(b),
70            (CypherValue::String(a), CypherValue::String(b)) => a.partial_cmp(b),
71            (CypherValue::List(a), CypherValue::List(b)) => a.partial_cmp(b),
72            (CypherValue::Map(_), CypherValue::Map(_)) => None,
73            // Cross-type ordering (arbitrary but consistent)
74            (CypherValue::Boolean(_), _) => Some(Ordering::Less),
75            (_, CypherValue::Boolean(_)) => Some(Ordering::Greater),
76            (CypherValue::Integer(_), _) => Some(Ordering::Less),
77            (_, CypherValue::Integer(_)) => Some(Ordering::Greater),
78            (CypherValue::Float(_), _) => Some(Ordering::Less),
79            (_, CypherValue::Float(_)) => Some(Ordering::Greater),
80            (CypherValue::String(_), _) => Some(Ordering::Less),
81            (_, CypherValue::String(_)) => Some(Ordering::Greater),
82            (CypherValue::List(_), _) => Some(Ordering::Less),
83            (_, CypherValue::List(_)) => Some(Ordering::Greater),
84        }
85    }
86}
87
88/// A node pattern such as `(n:Person {name: "Alice"})`.
89#[derive(Debug, Clone, PartialEq)]
90pub struct NodePattern {
91    /// Optional bound variable name (e.g. `n`).
92    pub variable: Option<String>,
93    /// Zero or more node labels (e.g. `["Person"]`).
94    pub labels: Vec<String>,
95    /// Inline property map (e.g. `{name: "Alice"}`).
96    pub properties: HashMap<String, CypherValue>,
97}
98
99/// The direction of a relationship pattern.
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum RelDirection {
102    /// `-[...]->`  – relationship points right (source → target).
103    Right,
104    /// `<-[...]-`  – relationship points left (target ← source).
105    Left,
106    /// `-[...]-`   – relationship is undirected / direction-agnostic.
107    Both,
108}
109
110/// A relationship pattern such as `-[:KNOWS {since: 2020}]->`.
111#[derive(Debug, Clone, PartialEq)]
112pub struct RelPattern {
113    /// Optional bound variable name (e.g. `r`).
114    pub variable: Option<String>,
115    /// Optional relationship type (e.g. `"KNOWS"`).
116    pub rel_type: Option<String>,
117    /// Inline property map.
118    pub properties: HashMap<String, CypherValue>,
119    /// Direction of the relationship.
120    pub direction: RelDirection,
121    /// Variable-length relationship bounds, if specified.
122    pub length: Option<RelationshipLength>,
123}
124
125/// Length bounds for variable-length relationships.
126#[derive(Debug, Clone, PartialEq, Eq)]
127pub struct RelationshipLength {
128    /// Minimum hops (inclusive). `None` means 1 for `*n..m`, 0 for `*`.
129    pub min: Option<usize>,
130    /// Maximum hops (inclusive). `None` means unbounded.
131    pub max: Option<usize>,
132}
133
134/// A path pattern: a start node followed by zero or more
135/// (relationship, node) pairs.
136///
137/// Example: `(a)-[:KNOWS]->(b)-[:LIKES]->(c)`
138#[derive(Debug, Clone, PartialEq)]
139pub struct PathPattern {
140    /// The first node in the path.
141    pub start: NodePattern,
142    /// Each subsequent hop: `(relationship_pattern, target_node_pattern)`.
143    pub rels: Vec<(RelPattern, NodePattern)>,
144}
145
146/// An item in a `RETURN` or `WITH` clause.
147#[derive(Debug, Clone, PartialEq)]
148pub struct ReturnItem {
149    /// The expression being returned.
150    pub expression: Expression,
151    /// An optional `AS alias` name.
152    pub alias: Option<String>,
153}
154
155/// Unary operators.
156#[derive(Debug, Clone, Copy, PartialEq, Eq)]
157pub enum UnaryOp {
158    /// Logical negation (`NOT`).
159    Not,
160    /// Arithmetic negation (`-`).
161    Negate,
162    /// Unary plus (`+`, no-op).
163    Plus,
164}
165
166/// Binary operators.
167#[derive(Debug, Clone, Copy, PartialEq, Eq)]
168pub enum BinaryOp {
169    /// `+`
170    Add,
171    /// `-`
172    Subtract,
173    /// `*`
174    Multiply,
175    /// `/`
176    Divide,
177    /// `%`
178    Modulo,
179    /// `^`
180    Power,
181    /// `=`
182    Eq,
183    /// `<>` / `!=`
184    Ne,
185    /// `<`
186    Lt,
187    /// `>`
188    Gt,
189    /// `<=`
190    Le,
191    /// `>=`
192    Ge,
193    /// `AND`
194    And,
195    /// `OR`
196    Or,
197    /// `XOR`
198    Xor,
199    /// `STARTS WITH`
200    StartsWith,
201    /// `ENDS WITH`
202    EndsWith,
203    /// `CONTAINS`
204    Contains,
205    /// `IN`
206    In,
207}
208
209/// An expression that can appear in a `RETURN` clause or `WHERE` condition.
210#[derive(Debug, Clone, PartialEq)]
211pub enum Expression {
212    /// A bare variable reference (e.g. `n`).
213    Variable(String),
214    /// A property access (e.g. `n.name`).
215    Property(String, String),
216    /// The wildcard `*` (return everything).
217    All,
218    /// A literal value.
219    Literal(CypherValue),
220    /// A unary operation.
221    Unary(UnaryOp, Box<Expression>),
222    /// A binary infix operation.
223    Binary(BinaryOp, Box<Expression>, Box<Expression>),
224    /// A list literal `[e1, e2, …]`.
225    List(Vec<Expression>),
226    /// A function or aggregate call.
227    FunctionCall {
228        /// Function name.
229        name: String,
230        /// Positional arguments.
231        args: Vec<Expression>,
232        /// `true` when called as `f(DISTINCT …)`.
233        distinct: bool,
234    },
235    /// A parameter reference (`$name`).
236    Parameter(String),
237    /// A `CASE … END` expression.
238    Case(CaseExpr),
239}
240
241/// A `CASE … END` expression.
242#[derive(Debug, Clone, PartialEq)]
243pub struct CaseExpr {
244    /// The scrutinee for generic `CASE expr WHEN …`, or `None` for searched form.
245    pub scrutinee: Option<Box<Expression>>,
246    /// The `WHEN … THEN …` alternatives.
247    pub alternatives: Vec<(Expression, Expression)>,
248    /// The `ELSE` default, or `None`.
249    pub default: Option<Box<Expression>>,
250}
251
252/// A basic `WHERE` expression (subset of OpenCypher).
253#[derive(Debug, Clone, PartialEq)]
254pub enum WhereExpr {
255    /// An equality check: `n.prop = value`.
256    Eq(Expression, Expression),
257    /// A not-equal check: `n.prop <> value`.
258    NotEq(Expression, Expression),
259    /// Less than: `n.prop < value`.
260    Lt(Expression, Expression),
261    /// Greater than: `n.prop > value`.
262    Gt(Expression, Expression),
263    /// Less than or equal: `n.prop <= value`.
264    Le(Expression, Expression),
265    /// Greater than or equal: `n.prop >= value`.
266    Ge(Expression, Expression),
267    /// List membership: `n.prop IN […]`.
268    In(Expression, Vec<CypherValue>),
269    /// String starts with: `n.prop STARTS WITH "prefix"`.
270    StartsWith(Expression, String),
271    /// String ends with: `n.prop ENDS WITH "suffix"`.
272    EndsWith(Expression, String),
273    /// String contains: `n.prop CONTAINS "substr"`.
274    Contains(Expression, String),
275    /// `IS NULL` check.
276    IsNull(Expression),
277    /// `IS NOT NULL` check.
278    IsNotNull(Expression),
279    /// Logical NOT of a sub-expression.
280    Not(Box<WhereExpr>),
281    /// Logical AND of two sub-expressions.
282    And(Box<WhereExpr>, Box<WhereExpr>),
283    /// Logical OR of two sub-expressions.
284    Or(Box<WhereExpr>, Box<WhereExpr>),
285    /// Logical XOR of two sub-expressions.
286    Xor(Box<WhereExpr>, Box<WhereExpr>),
287}
288
289/// Sort direction for `ORDER BY`.
290#[derive(Debug, Clone, Copy, PartialEq, Eq)]
291pub enum SortDirection {
292    Asc,
293    Desc,
294}
295
296/// A single `ORDER BY` criterion.
297#[derive(Debug, Clone, PartialEq)]
298pub struct SortItem {
299    /// The expression to sort by.
300    pub expression: Expression,
301    /// The sort direction.
302    pub direction: SortDirection,
303}
304
305/// An item in a `SET` clause.
306#[derive(Debug, Clone, PartialEq)]
307pub enum SetItem {
308    /// `variable.property = value`
309    SetProperty {
310        variable: String,
311        property: String,
312        value: Expression,
313    },
314    /// `variable = map` (replace all properties)
315    SetVariable {
316        variable: String,
317        properties: HashMap<String, CypherValue>,
318    },
319    /// `variable:Label1:Label2` — add labels.
320    SetLabels {
321        variable: String,
322        labels: Vec<String>,
323    },
324    /// `variable += map` (merge properties)
325    MergeProperties {
326        variable: String,
327        properties: HashMap<String, CypherValue>,
328    },
329}
330
331/// An item in a `REMOVE` clause.
332#[derive(Debug, Clone, PartialEq)]
333pub enum RemoveItem {
334    /// Remove a property.
335    RemoveProperty { variable: String, property: String },
336    /// Remove labels from a node.
337    RemoveLabels {
338        variable: String,
339        labels: Vec<String>,
340    },
341}
342
343/// A single Cypher clause.
344#[derive(Debug, Clone, PartialEq)]
345pub enum Clause {
346    /// `MATCH pattern [WHERE condition]`
347    Match {
348        patterns: Vec<PathPattern>,
349        where_clause: Option<WhereExpr>,
350    },
351    /// `OPTIONAL MATCH pattern [WHERE condition]`
352    OptionalMatch {
353        patterns: Vec<PathPattern>,
354        where_clause: Option<WhereExpr>,
355    },
356    /// `CREATE pattern`
357    Create { patterns: Vec<PathPattern> },
358    /// `MERGE pattern [ON CREATE SET …] [ON MATCH SET …]`
359    Merge {
360        pattern: PathPattern,
361        on_create: Vec<SetItem>,
362        on_match: Vec<SetItem>,
363    },
364    /// `RETURN items`
365    Return {
366        items: Vec<ReturnItem>,
367        distinct: bool,
368    },
369    /// `WITH items`
370    With {
371        items: Vec<ReturnItem>,
372        where_clause: Option<WhereExpr>,
373        order_by: Option<Vec<SortItem>>,
374        skip: Option<usize>,
375        limit: Option<usize>,
376        distinct: bool,
377    },
378    /// `UNWIND list AS variable`
379    Unwind {
380        expression: Expression,
381        variable: String,
382    },
383    /// `[DETACH] DELETE variables`
384    Delete {
385        variables: Vec<String>,
386        detach: bool,
387    },
388    /// `SET items`
389    Set { items: Vec<SetItem> },
390    /// `REMOVE items`
391    Remove { items: Vec<RemoveItem> },
392    /// `ORDER BY items`
393    OrderBy { items: Vec<SortItem> },
394    /// `SKIP n`
395    Skip { count: usize },
396    /// `LIMIT n`
397    Limit { count: usize },
398}
399
400/// A parsed OpenCypher query consisting of one or more clauses.
401#[derive(Debug, Clone, PartialEq)]
402pub struct CypherQuery {
403    /// The ordered list of clauses in this query.
404    pub clauses: Vec<Clause>,
405}