A small, dependency-free lexer, parser, and pluggable executor for a practical subset of the Cypher graph query language.
It has two layers:
- Parsing turns a query string into an AST (or a positioned error).
- Execution evaluates a parsed query against any backend that implements the
[
GraphProvider] trait, returning a result set you can render as a table or JSON. The executor is generic and reads your graph only through that trait.
It targets read-only, introspection-style queries.
use cypher_parser::parse;
let query = parse(
"MATCH (c:Class)-[:INHERITS*1..]->(p:Class {name: 'Base'}) \
WHERE c.name CONTAINS 'Service' \
RETURN c.name, count(*) AS total \
ORDER BY total DESC LIMIT 10",
)?;
for item in &query.result.items {
println!("{}", item.column_name());
}
# Ok::<(), cypher_parser::CypherError>(())Implement [GraphProvider] for your data structure, then call run_query (or execute for a
structured ResultSet):
use cypher_parser::{run_query, GraphProvider, CypherValue, OutputFormat};
impl GraphProvider for MyGraph {
type NodeId = usize;
fn scan(&self, labels: &[String]) -> Vec<usize> { /* ... */ }
fn matches_label(&self, node: usize, label: &str) -> bool { /* ... */ }
fn relationship_types(&self) -> Vec<String> { /* ... */ }
fn expand(&self, node: usize, rel_type: &str) -> Vec<usize> { /* ... */ }
fn rel_sources(&self, rel_type: &str) -> Vec<usize> { /* ... */ }
fn property(&self, node: usize, prop: &str) -> CypherValue { /* ... */ }
fn node_id(&self, node: usize) -> String { /* stable, opaque identity */ }
fn label(&self, node: usize) -> String { /* ... */ }
fn name(&self, node: usize) -> String { /* ... */ }
}
let output = run_query(&my_graph, "MATCH (n:Class) RETURN n.name", OutputFormat::Json)?;Providers may also implement the optional expand_in method to answer incoming-neighbour queries
directly; it makes incoming and undirected relationship steps O(degree) instead of building a
whole-graph reverse-adjacency map. It defaults to None (fall back to the reverse build), so it is
never required.
MATCH— node patterns(v:Label {prop: value})with label disjunction ((v:A|B)); relationship patterns-[:TYPE]->,<-[:TYPE]-,-[:TYPE]-, including variable-length-[:TYPE*min..max]->.WHERE—=,<>,<,<=,>,>=,CONTAINS,STARTS WITH,ENDS WITH,IN(with[...]list literals),IS NULL/IS NOT NULL, combined withAND,OR,NOT.- Scalar functions —
toLower,toUpper,size,coalesce,labels. RETURN—DISTINCT,*(all bound variables),ASaliases, and the aggregatescount,collect,min,max,sum,avg.WITH— chain clauses by projecting into new bindings (same features asRETURN, including aggregates) with an optional trailingWHERE, e.g.MATCH ... WITH n, count(*) AS c WHERE c > 1.OPTIONAL MATCH— left join; unmatched rows are kept with the clause's new variables null.UNWIND <list> AS x— expand a list into rows.EXISTS { [MATCH] pattern [WHERE ...] }— existential subquery predicate, andNOT EXISTS.CASE— bothCASE x WHEN v THEN ...andCASE WHEN cond THEN ...forms.- Map projections —
n { .prop, key: expr }. ORDER BY,SKIP,LIMIT(on bothWITHandRETURN).- Comments — line (
//) and block (/* ... */). - Negative integer literals —
-10.
Write clauses (CREATE, MERGE, SET, DELETE, REMOVE) are intentionally unsupported.
parse— parse a query string into aQueryAST.GraphProvider— implement this for your graph to make it queryable.execute— run a parsed query, returning aResultSet.run_query— parse + execute + format in one call.OutputFormat/CypherValue— result formatting and values.ast— the AST types;CypherError— lexing/parsing/execution errors with a source position.
Planned syntax additions include multi-label AND ((n:A:B)), floats and arithmetic, =~ regex
matching, query parameters ($name), UNION, and relationship-variable RETURN. Write clauses
remain out of scope.
Contributions are welcome! Please read CONTRIBUTING.md first. All contributors must sign the Shopify Contributor License Agreement (a bot will prompt you on your first pull request), and participation is governed by our Code of Conduct.
This project is maintained by Shopify and released under the MIT License.