GQL Queries
Experimental. GQL support is new in Graphersal 0.2.2 and marked experimental: its subset grows from version to version, and the answers of a construct (its rows, errors and plans) may still change in a later version. Gremlin stays the complete, stable interface: a
gql()query runs as ordinary Gremlin steps, and this chapter shows the Gremlin twin of each construct.
g.gql(query) runs a query written in GQL, the ISO standard graph query language
(ISO/IEC 39075), on the same graph and the same engine as Gremlin. A GQL query describes the
pattern you are looking for; Graphersal finds every match and returns one row per match:
g.gql("MATCH (p:person)-[:created]->(s:software) WHERE p.age > 30 RETURN p.name AS who, s.name AS what ORDER BY who, what").to_list()
// #{"what": "lop", "who": "josh"}, #{"what": "ripple", "who": "josh"}, #{"what": "lop", "who": "peter"}
The Gremlin equivalent walks the graph step by step:
g.v().has_label("person").has("age", P.gt(30)).as("p").out("created").has_label("software").as("s").select("p", "s").by("name").to_list()
// #{"p": "josh", "s": "ripple"}, #{"p": "josh", "s": "lop"}, #{"p": "peter", "s": "lop"}
Every example in this chapter was run on the sample graph modern; the comment after // is
its real output (a list is shown as its items, comma-separated, or one per line).
What gql() is
- ISO GQL is the norm. Queries are written in GQL; where GQL and Cypher differ, GQL wins.
Familiar Cypher spellings are accepted as aliases where they are unambiguous and mean the
same (
SKIPforOFFSET,!=for<>,toUpper()forupper(),-->for->, ...); Cypher Aliases lists every one. There is no Graphersal-only syntax: an accepted query means the same in a GQL or Cypher 25 database (modulo the listed aliases). - Reads and writes.
INSERT(CypherCREATE) creates nodes and edges,SETandREMOVEchange their properties and labels, andDELETE/DETACH DELETEremove them, each once per row, and Cypher'sMERGEfinds or creates a pattern (Writing with GQL). A query is one unit: a query that fails changes nothing. - A start step.
gql()starts a traversal:g.gql(..), neverg.v().gql(..)or__.gql(..). - Rows are maps. Each result row is a map from column name to value
(Result Shape).
RETURNis mandatory unless the query ends in a write. - One engine. The query is compiled into an ordinary traversal plan: the same optimizer,
property indexes,
profile(), limits, cancellation, permissions and storages as Gremlin (Plans and Performance). A query that does not parse fails before anything runs, with a caret under the offending text (Errors).
This version supports INSERT and GQL read queries: MATCH and OPTIONAL MATCH, WHERE, LET, FILTER,
Cypher's WITH, RETURN with aggregation (count, sum, avg, min, max, collect_list),
ORDER BY, OFFSET, LIMIT, FOR (Cypher UNWIND) with a direct lookup per item where an
id or index serves it, queries that start without MATCH (RETURN 1 + 2 AS x), EXISTS
subqueries and Cypher pattern predicates (WHERE NOT (p)-[:created]->()), Cypher map projections
and list and pattern comprehensions for nested, JSON-shaped answers
(p {.name, friends: [(p)-[:knows]->(f) | f.name]}), Cypher's COUNT { } / COLLECT { } and
list predicates (all(), any(), reduce(), ...), composite queries (UNION, UNION ALL,
NEXT), CALL { } subqueries run per row (top k per group), node and edge
patterns, quantified edges (-[:knows]->{1,3}), quantified path patterns that repeat a whole
chain ((a) ((x)-[:created]->(s)<-[:created]-(y)){1,2} (b)), path variables, label expressions,
expressions, parameters, the GQL scalar functions and the common openCypher ones (toInteger,
substring, split, round, keys, nodes(p), ...) (The Supported Subset).
EXCEPT / INTERSECT, shortest paths and a quantifier nested inside a quantified path pattern
come later; until then the error names the form or the Gremlin step that does the same.
g.gql("MATCH (p:person) OPTIONAL MATCH (p)-[:created]->(s) RETURN p.name AS name, count(s) AS made ORDER BY made DESC, name").to_list()
// josh 2, marko 1, peter 1, vadas 0
g.gql("MATCH (a {name: 'marko'})-[:knows]->{1,2}(b)-[:created]->(s) RETURN DISTINCT s.name AS s ORDER BY s").to_list() // #{"s": "lop"}, #{"s": "ripple"}
g.gql("MATCH (p:person) CALL (p) { MATCH (p)-[e:created]->(s) RETURN s.name AS top ORDER BY e.weight DESC LIMIT 1 } RETURN p.name AS who, top ORDER BY who").to_list()
// josh ripple, marko lop, peter lop: each person's heaviest software
Mixing GQL and Gremlin
Gremlin steps may follow gql(). They see the row maps and reach a column by its name, so
whatever GQL cannot do yet, a Gremlin step after it can:
g.gql("MATCH (p:person) RETURN p ORDER BY p.name").select("p").out("created").values("name").order().to_list() // "lop", "lop", "lop", "ripple"
g.gql("MATCH (a)-[:knows]->(b) RETURN *").select("a", "b").by("name").to_list() // #{"a": "marko", "b": "josh"}, #{"a": "marko", "b": "vadas"}
g.gql("MATCH (p:person) RETURN p.name AS name, p.age AS age").select("age").sum().next() // 123
g.gql("MATCH (p:person)-[:created]->(s) RETURN p.name AS name").select("name").dedup().order().to_list() // "josh", "marko", "peter"
An element column (RETURN p) holds the live vertex or edge, so select("p").out(..) continues
the walk from it.
Where you can use it
| Entry point | How |
|---|---|
| Rust | GraphTraversalSource::gql(query) and gql_with(query, params), feature gql |
| Gremlin DSL (Rhai) | g.gql(query) and g.gql(query, #{name: value}); see Writing GQL in Rhai Strings |
| Command line | graphersal -e 'g.gql("MATCH (p:person) RETURN p.name AS name")', or in the REPL |
| Python | through a script: graph.query('g.gql("...", #{...})', params={...}) |
| Playground | a GQL query tab (the + menu, or the Rhai / GQL switch): plain GQL with completion and a Parameters field; or g.gql("...") in a Rhai tab; the examples list has a GQL group (Web Playground) |
| Saved queries | g.define_query(#{name: "older", language: "gql", params: #{min: "integer"}, body: "MATCH (p:person) WHERE p.age > $min RETURN p.name AS name"}), then g.query("older", #{min: 30}) from any front end (Saved queries in GQL), or CALL older(30) YIELD name RETURN name from a GQL query (Calling saved queries) |
| MCP (dev server) | the tool gql {query, params, profile, offset, max_rows, max_items}: the query as plain text, no Rhai string around it, its rows as compact JSON paged with offset; it writes like the query tool, and a server started with --mcp-read-only refuses its writes |
In Rust:
use graphersal::prelude::*;
let graph = TraversalGraph::tinkerpop_modern();
let ages = graph
.traversal()
.gql_with(
"MATCH (p:person {name: $name}) RETURN p.age AS age",
[("name", ElementProperty::from("marko"))],
)
.select("age")
.to_list()
.unwrap();
assert_eq!(ages, [ElementProperty::from(29)]);
A writing query runs on the writing source, traversal_mut() (In Rust).
In the playground, a GQL tab takes the query as it is, without a Rhai string around it, and its parameters as a JSON map in the Parameters field under the editor:
MATCH (p:person) WHERE p.age > $min
RETURN p.name AS name ORDER BY name
Parameters: {"min": 30}
The tab runs it as g.gql("MATCH (p:person) WHERE p.age > $min\nRETURN p.name AS name ORDER BY name", #{min: 30}):
a JSON integer stays an integer, 30.0 is a float, null is NULL, lists and maps stay lists and
maps. An error is underlined where the engine reports it, with its GQLSTATUS in the message. To
continue a GQL result with Gremlin steps (.select("p").out()), switch the tab to Rhai: the query
and its parameters become that g.gql(..) call.
The feature gql is off by default in the graphersal crate (Library Features);
the command line, the playground, the dev server and the Python package have it on.
From Python, values travel as script variables and from there into the GQL parameter map:
graph.query('g.gql("MATCH (p:person) WHERE p.age > $min RETURN p.name AS name, p.age AS age ORDER BY age", #{min: min_age})',
params={"min_age": 30})
# [{'age': 32, 'name': 'josh'}, {'age': 35, 'name': 'peter'}]
In this chapter
- The Supported Subset: clauses, patterns, label expressions, expressions, functions, parameters, and what is not supported yet.
- Semantics: trail matching, quantified edges and path patterns and their
limits,
OPTIONAL MATCH,NULLand three-valued logic, subqueries (EXISTS,COUNT,COLLECT,CALL),UNIONandNEXT, aggregation,DISTINCTequality, comparisons, ordering, the rules of the functions (strictCAST, lenienttoInteger, rounding, where they differ from Neo4j), ids. - Result Shape: row maps, column names and their order, path values.
- Writing with GQL:
INSERT(CypherCREATE), what a row sees, units, schema, permissions, and the write clauses that come later. - Writing GQL in Rhai Strings: quotes, multi-line queries, comments, parameters, and when the MCP tool is easier.
- Cypher Aliases: every accepted Cypher spelling and its GQL form.
- Refused Constructs: what
gql()will not do, and why. - Errors: GQLSTATUS codes and diagnostics.
- Plans and Performance: what a query becomes in
profile(). - GQL Conformance: the GQL features supported, partially supported and not supported.
- The generated step page gql.