Skip to main content

petgraph_decypher/
lib.rs

1//! `petgraph-decypher` – build [`petgraph`] graphs from OpenCypher queries.
2//!
3//! # Overview
4//!
5//! This crate provides three main entry points:
6//!
7//! * [`parse_cypher`] – parse a Cypher query string into the internal query plan.
8//! * [`build_graph_from_cypher`] – parse a Cypher query and materialise all
9//!   `CREATE` / `MERGE` operations into a [`petgraph::Graph`] with the default
10//!   [`NodeData`] / [`EdgeData`] types.
11//! * [`build_graph_from_cypher_typed`] – generic version that accepts any types
12//!   implementing [`CypherNode`] and [`CypherEdge`].
13//!
14//! # Supported Cypher subset
15//!
16//! | Feature | Status |
17//! |---------|--------|
18//! | `CREATE (n:Label {k: v})-[:TYPE]->(m)` | ✅ materialised + executed |
19//! | `MERGE  (n:Label {k: v})-[:TYPE]->(m)` | ✅ materialised + executed |
20//! | `MATCH  (n)-[r]->(m) WHERE n.p = v`    | ✅ evaluated |
21//! | `RETURN n, n.prop AS alias` / `RETURN *` | ✅ evaluated |
22//! | `[DETACH] DELETE n`                    | ✅ executed |
23//! | Multiple clauses in one query          | ✅ |
24//! | Semicolon-separated statements         | ✅ |
25//!
26//! # Example
27//!
28//! ```rust
29//! use petgraph_decypher::build_graph_from_cypher;
30//!
31//! let graph = build_graph_from_cypher(
32//!     r#"CREATE (a:Person {name: "Alice"})-[:KNOWS]->(b:Person {name: "Bob"})"#,
33//! )
34//! .unwrap();
35//!
36//! assert_eq!(graph.node_count(), 2);
37//! assert_eq!(graph.edge_count(), 1);
38//! ```
39
40pub mod ast;
41mod builder;
42pub mod error;
43mod planner;
44pub mod query;
45
46pub use ast::{
47    BinaryOp, CaseExpr, Clause, CypherQuery, CypherValue, Expression, NodePattern, PathPattern,
48    RelDirection, RelPattern, RelationshipLength, ReturnItem, SetItem, SortDirection, SortItem,
49    UnaryOp, WhereExpr,
50};
51pub use error::CypherError;
52pub use query::{MatchStrategy, Parameters, PetgraphCypher, QueryResult, ResultValue, Row};
53
54use petgraph::Graph;
55use std::collections::HashMap;
56
57/// Data stored at each node in the graph built by [`build_graph_from_cypher`].
58#[derive(Debug, Clone, PartialEq)]
59pub struct NodeData {
60    /// Bound variable name from the Cypher pattern (e.g. `"n"`), if present.
61    pub variable: Option<String>,
62    /// Node labels (e.g. `["Person", "Employee"]`).
63    pub labels: Vec<String>,
64    /// Properties specified in the node pattern.
65    pub properties: HashMap<String, CypherValue>,
66}
67
68/// Common property access used by the query planner and executor.
69pub trait CypherProperties {
70    /// Get a property value by name.
71    fn get(&self, name: &str) -> Option<&CypherValue>;
72
73    /// Return all properties as a cloned map for query results.
74    fn properties(&self) -> HashMap<String, CypherValue>;
75}
76
77/// Metadata required from node weights during Cypher planning and execution.
78pub trait CypherNode: CypherProperties {
79    /// Returns whether this node has the requested label.
80    fn has_label(&self, label: &str) -> bool;
81
82    /// Return all node labels.
83    fn labels(&self) -> Vec<String>;
84
85    /// Construct a node value from a Cypher pattern.
86    fn from_cypher(
87        variable: Option<String>,
88        labels: Vec<String>,
89        properties: HashMap<String, CypherValue>,
90    ) -> Self;
91}
92
93/// Data stored at each edge in the graph built by [`build_graph_from_cypher`].
94#[derive(Debug, Clone, PartialEq)]
95pub struct EdgeData {
96    /// Bound variable name from the Cypher pattern (e.g. `"r"`), if present.
97    pub variable: Option<String>,
98    /// Relationship type (e.g. `"KNOWS"`), if specified.
99    pub rel_type: Option<String>,
100    /// Properties specified in the relationship pattern.
101    pub properties: HashMap<String, CypherValue>,
102}
103
104/// Metadata required from edge weights during Cypher planning and execution.
105pub trait CypherEdge: CypherProperties {
106    /// Returns whether this edge matches the requested relationship type.
107    fn has_rel_type(&self, rel_type: &str) -> bool;
108
109    /// Return the relationship type, if any.
110    fn rel_type(&self) -> Option<&str>;
111
112    /// Construct an edge value from a Cypher pattern.
113    fn from_cypher(
114        variable: Option<String>,
115        rel_type: Option<String>,
116        properties: HashMap<String, CypherValue>,
117    ) -> Self;
118}
119
120impl CypherProperties for NodeData {
121    fn get(&self, name: &str) -> Option<&CypherValue> {
122        self.properties.get(name)
123    }
124
125    fn properties(&self) -> HashMap<String, CypherValue> {
126        self.properties.clone()
127    }
128}
129
130impl CypherNode for NodeData {
131    fn has_label(&self, label: &str) -> bool {
132        self.labels.iter().any(|node_label| node_label == label)
133    }
134
135    fn labels(&self) -> Vec<String> {
136        self.labels.clone()
137    }
138
139    fn from_cypher(
140        variable: Option<String>,
141        labels: Vec<String>,
142        properties: HashMap<String, CypherValue>,
143    ) -> Self {
144        Self {
145            variable,
146            labels,
147            properties,
148        }
149    }
150}
151
152impl CypherProperties for EdgeData {
153    fn get(&self, name: &str) -> Option<&CypherValue> {
154        self.properties.get(name)
155    }
156
157    fn properties(&self) -> HashMap<String, CypherValue> {
158        self.properties.clone()
159    }
160}
161
162impl CypherEdge for EdgeData {
163    fn has_rel_type(&self, rel_type: &str) -> bool {
164        self.rel_type.as_deref() == Some(rel_type)
165    }
166
167    fn rel_type(&self) -> Option<&str> {
168        self.rel_type.as_deref()
169    }
170
171    fn from_cypher(
172        variable: Option<String>,
173        rel_type: Option<String>,
174        properties: HashMap<String, CypherValue>,
175    ) -> Self {
176        Self {
177            variable,
178            rel_type,
179            properties,
180        }
181    }
182}
183
184/// Parse a Cypher query string and return its HIR-backed query plan representation.
185///
186/// # Errors
187///
188/// Returns [`CypherError::ParseError`] if the input cannot be parsed.
189///
190/// # Example
191///
192/// ```rust
193/// use petgraph_decypher::parse_cypher;
194///
195/// let query = parse_cypher("CREATE (n:Person {name: \"Alice\"})").unwrap();
196/// assert_eq!(query.clauses.len(), 1);
197/// ```
198pub fn parse_cypher(query: &str) -> Result<CypherQuery, CypherError> {
199    planner::plan_query(query)
200}
201
202/// Parse a Cypher query and build a petgraph [`Graph`] from its `CREATE` and
203/// `MERGE` clauses using custom node and edge types.
204///
205/// This is the generic version of [`build_graph_from_cypher`]. Use it when you
206/// want to supply your own types that implement [`CypherNode`] and [`CypherEdge`].
207///
208/// Each distinct variable in the query corresponds to a single node; if the
209/// same variable appears in multiple patterns it maps to the same
210/// [`petgraph::graph::NodeIndex`].
211///
212/// # Errors
213///
214/// Returns [`CypherError::ParseError`] if the input cannot be parsed.
215///
216/// # Example
217///
218/// ```rust
219/// use petgraph_decypher::{build_graph_from_cypher_typed, NodeData, EdgeData};
220///
221/// let graph = build_graph_from_cypher_typed::<NodeData, EdgeData>(
222///     r#"CREATE (a:Person {name: "Alice"})-[:KNOWS]->(b:Person {name: "Bob"})"#,
223/// )
224/// .unwrap();
225///
226/// assert_eq!(graph.node_count(), 2);
227/// assert_eq!(graph.edge_count(), 1);
228/// ```
229pub fn build_graph_from_cypher_typed<N: CypherNode, E: CypherEdge>(
230    query: &str,
231) -> Result<Graph<N, E>, CypherError> {
232    let ast = parse_cypher(query)?;
233    builder::build_graph(ast)
234}
235
236/// Parse a Cypher query and build a petgraph [`Graph`] from its `CREATE` and
237/// `MERGE` clauses.
238///
239/// Each distinct variable in the query corresponds to a single node; if the
240/// same variable appears in multiple patterns it maps to the same
241/// [`petgraph::graph::NodeIndex`].
242///
243/// # Errors
244///
245/// Returns [`CypherError::ParseError`] if the input cannot be parsed.
246///
247/// # Example
248///
249/// ```rust
250/// use petgraph_decypher::{build_graph_from_cypher, CypherValue};
251///
252/// let graph = build_graph_from_cypher(
253///     r#"CREATE (a:Person {name: "Alice"})-[:KNOWS]->(b:Person {name: "Bob"})"#,
254/// )
255/// .unwrap();
256///
257/// assert_eq!(graph.node_count(), 2);
258/// assert_eq!(graph.edge_count(), 1);
259///
260/// // Check the edge type
261/// let edge = graph.edge_indices().next().unwrap();
262/// assert_eq!(graph[edge].rel_type.as_deref(), Some("KNOWS"));
263/// ```
264pub fn build_graph_from_cypher(query: &str) -> Result<Graph<NodeData, EdgeData>, CypherError> {
265    build_graph_from_cypher_typed(query)
266}