Query DSL Expressions - Wolfgang-Schuetzelhofer/jcypher GitHub Wiki
Previous | Next | Table of Contents |
Expressions of JCypher-Query-DSL are logically organized in 4 layers.
We will examine the 4 layers bottom up, explaining their role in the context of the query language, underpinned with short examples. The root package for all query related classes is iot.jcypher.query
.
Values are elements of the 'Property Graph'.
There are primitive values like e.g. 'strings' or 'numbers' (they are mostly found as values of properties in nodes and relations), and there are complex values, which are 'Nodes', 'Relations' (aka 'Relationships' in Cypher), and 'Paths'. Operations and Functions performed on these values, thereby yielding other values, are called 'Value Expressions'.
Primitive values are implemented by classes JcString
, JcNumber
,..., and complex values are implemented by classes JcNode
, JcRelation
, JcPath
.
A value expression is constructed starting with an instance of a value class. Value classes implement methods which can be fluently concatenated in order to formulate value expressions. Constructors of value classes take a string argument which represents an identifier.The package iot.jcypher.query.values
contains classes which handle values and value expressions.
//Nodes are represented by class JcNode
JcNode n = new JcNode("n"); //"n" is the node's identifier.
//Then we can formulate:
n.stringProperty("name").length();
//which accesses the node's property "name" as a string
//and calculates its length.
JcNode a = new JcNode("a");
JcNode b = new JcNode("b");
a.stringProperty("name").concat("<->").concat(b.stringProperty("name"));
//concatenates the "name" properties of two nodes (a, b)
//with a string constant.
/*This maps to the Cypher expression:*/ a.name + '<->' + b.name
a.numberProperty("amount").plus(b.numberProperty("amount"));
//calculates the sum of the "amount" properties of two nodes (a, b).
//Given that 'r' is a 'Relation', represented by class JcRelation.
JcRelation r = new JcRelation("r"); //then
r.startNode(); //answers the start node of the relation.
Graph Expressions use values and value expressions as arguments in order to formulate higher order expressions. That means, these expressions target greater portions of the graph, sometimes even the entire graph, rather than distinct values.
You will find two packages iot.jcypher.query.api
and iot.jcypher.query.ast
. The first one handles the API aspect of graph expressions. It contains classes which implement the fluent API used to create graph expressions.
The second one contains code that is used in the background to construct the AST (Abstract Syntax Tree), which is the actual model of the expressions. Code that processes JCypher-Query-DSL expressions, such as the mapper to Cypher, works upon this model.
You start formulating graph expressions in one of two possible ways:
Either you start with a clause factory class:
MATCH.node(director).property("name")
.value("Oliver Stone").relation().node(movie)
Package iot.jcypher.query.factories.clause
contains the clause factory classes (MATCH
, WHERE
, RETURN
, WITH
, ...).
Or you start with an expression factory class (Class C below):
WHERE.valueOf(persons.property("name"))
.IN(C.COLLECT().property("name").from(friends))
Package iot.jcypher.query.factories.xpression
contains the expression factory classes (C
.. for collections, P
.. for predicates, ...).
You use an expression factory class when graph expressions are combined (one graph expression contains another graph expression of different type). In the example above a predicate expression contains a collection expression. The factory class for collection expressions is C.
There are several types of graph expressions used to query or update different aspects of the graph.
-
Pattern Expressions
Are used to formulate Patterns (of nodes, relations, or paths) to be matched against or created within the graph.
JcNode director = new JcNode("director");
JcNode movie = new JcNode("movie");
...node(director).property("name").value("Oliver Stone")
.relation().out().node(movie);
//can be used to match or create portions of a graph.
-
Predicate Expressions
Serve to express and evaluate predicates (evaluating a predicate results in a boolean value).
...valueOf(n.property("firstName")).EQUALS("charlie").AND()...
-
Collection Expressions
Return collections of elements and allow to 'filter', 'extract', or otherwise manipulate the results.
EXTRACT().valueOf(n.property("age")).fromAll(n).IN(p.nodes())
//returns a collection containing the "age" properties
//of all nodes of path 'p'
-
Aggregate Expressions
Work on collections and calculate aggregated values.
...aggregate().stdev(n.property("age"))
--- calculates the standard deviation for the "age" property over a collection of nodes (n must have previously in this query been matched to represent a collection of nodes).
-
Modification Expressions
Serve to modify node or relation properties and node labels.
DO.SET(n.property("surname")).to("Taylor");
-
Result Expressions
Are the means to specify what (in terms of evaluation results of expressions) should be returned from the query or should be used in subsequent parts of the query respectively. The specified evaluation results can furthermore be assigned to distinct identifiers. Additionally, some expressions are provided which serve to filter and order the result in case it is a collection.
//Note: WITH is a clause and defines which parts
//of a query (calculated so far) should be passed
//on to the following query parts
WITH.value(n.property("name")).AS(personName)
//evaluates the "name" property of node 'n' and
//assigns it to 'personName' (a `JcString`).
RETURN.value(p.nodes()).AS(allPersons).ORDER_BY("age")
//returns all nodes of path 'p', orders the result
//by property "age" (ascending) and
//assigns it to 'allPersons' (a `JcCollection`).
-
Index Expressions
Serve to create or drop indexes on node labels and to give hints on using indexes when querying a graph.
CREATE_INDEX.onLabel("Person").forProperty("name");
USING.INDEX("Person").on(n.property("name"));
Note: Expressions of Layer 1 are often combined. For example a collection expression can use (contain) a predicate expression in order to filter elements which hold true for that predicate. Or a predicate expression can contain a pattern expression, testing if a matching pattern exists in the graph.
Clauses are used to group 'Graph Expressions' (Layer 1 expressions), thus giving certain semantics to these expressions in the context of a query. The same graph expression or combination of graph expressions has different semantics in different clauses.
MATCH.node(movie).label("Movie")
//finds all nodes with label "Movie".
CREATE.node(movie).label("Movie")
//creates one node with label "Movie".
Note: In both cases the results are assigned to a JcNode
element 'movie' (which can also represent a collection of nodes as returned by the match clause) in order to be returned from the query or to be used in subsequent parts of the query.
In JCypher clauses are created by means of factory classes. There exists one factory class for each type of clause. The particular factory class is named after the clause, so that e.g. a class MATCH
serves as the factory class for MATCH clauses. You can find all factory classes for clauses in the package: iot.jcypher.query.factories.clause
.
The supported clauses are:
- START - defines starting points. A starting point is a relation or a node where a pattern is anchored. You can either introduce starting points by id, or by index lookups. START is optional.
- MATCH - allows you to specify the patterns that will be searched for in the database.
- OPTIONAL_MATCH - matches patterns against a graph database, just like MATCH does. The difference is that if no matches are found, OPTIONAL MATCH will use NULLs for missing parts of the pattern. Either the whole pattern is matched, or nothing is matched.
-
WHERE - is not a clause in it’s own right. Rather, it’s part of MATCH, OPTIONAL_MATCH, START or WITH.
In the case of WITH and START, WHERE simply filters the results.
For MATCH and OPTIONAL_MATCH on the other hand, WHERE adds constraints to the patterns described. It should not be seen as a filter after the matching is finished. - CREATE - creates graph elements (nodes and relations).
- CREATE_UNIQUE - is in the middle of MATCH and CREATE. It will match what it can, and create what is missing.
-
MERGE - ensures that a pattern exists in the graph. Either the pattern already exists, or it needs to be created.
MERGE either matches existing elements and binds them, or it creates new data and binds that. It’s like a combination of MATCH and CREATE that additionally allows you to specify what happens if the data was matched or created. This is done by:
* ON_CREATE.SET(...), ON_CREATE.REMOVE(...), ON_CREATE.DELETE(...) - which work like their counterparts in DO..() see below. or by:
* ON_MATCH.SET(...), ON_MATCH.REMOVE(...), ON_MATCH.DELETE(...) - also working like their DO..() counterparts. -
DO - lets you modify elements of the graph. DO comes in 3 different flavors.
* DO.SET(...) - update labels on nodes and properties on nodes and relations.
* DO.REMOVE(...) - remove labels from nodes and properties from nodes and relations.
* DO.DELETE(...) - delete graph elements (nodes and relations).
* DO.DETACH_DELETE(...) - delete nodes, before deleting those nodes, delete all relations which are attached to them. - FOR_EACH - allows to update elements in a collection ( a path, or a collection created by aggregation).
FOR_EACH.element(n).IN_nodes(p)
.DO().SET(n.property("marked")).to(true);
- RETURN - define which parts of a query should be returned. It can be nodes, relations, paths, properties or other values constructed by expressions in the query.
- WITH - define which parts of a query (calculated so far) should be passed on to the following query parts. It can be nodes, relations, paths, properties or other values constructed by expressions in the query. WITH is also used to separate reading from updating parts of a query. Every part of a query must be either read-only or write-only.
- SEPARATE - Certain clauses like e.g. MATCH, OPTIONAL_MATCH, CREATE, ..., when having multiple parts, are by default mapped to CYPHER being separated by commas. If instead you want each of the consecutive clauses of same type to start with the corresponding clause-keyword, you can use the SEPARATE clause (see example below).
clauses = new IClause[]
}
MATCH.node(movie).label("Movie"),
MATCH.node(director).property("name").value("Oliver Stone")
.relation().node(movie)
};
// maps to CYPHER:
MATCH (movie:Movie), (director{name:'Oliver Stone'})--(movie)
// in contrast
clauses = new IClause[]
}
MATCH.node(movie).label("Movie"),
SEPARATE.nextClause(),
MATCH.node(director).property("name").value("Oliver Stone")
.relation().node(movie)
};
// maps to CYPHER:
MATCH (movie:Movie)
MATCH (director{name:'Oliver Stone'})--(movie)
- UNION - combine results from multiple queries.
-
CASE - is a generic conditional expression, similar to if/else statements in other languages. It comes together with
* WHEN, ELSE, and END.
MATCH.node(n).label("Person"),
WHERE.valueOf(n.property("lastName")).EQUALS("Smith"),
CASE.result(),
WHEN.valueOf(n.property("firstName")).EQUALS("John"),
...,
WHEN.valueOf(n.property("firstName")).EQUALS("Angelina"),
...,
ELSE.perform(),
...,
END.caseXpr()
- CREATE_INDEX - create an index on a property for all nodes that have a certain label.
- DROP_INDEX - drop an index on all nodes that have a certain label.
- USING - give an index hint to force using a specific starting point.
- NATIVE - provide the ability to write plain CYPHER expressions. This is useful if certain Cypher expressions are not (yet) implemented in JCypher.
A Query resides at the topmost expression layer. A Query, an instance of class JcQuery
in JCypher, is a container for an ordered list (an array) of Clauses. It represents a unit of work which is performed against a graph database.
JcNode shakespeare = new JcNode("shakespeare");
JcNode playCaesar = new JcNode("playCaesar");
// construct the query
JcQuery query = new JcQuery();
query.setClauses(new IClause[] {
CREATE.node(shakespeare).property("lastname").value("Shakespeare"),
CREATE.node(playCaesar).property("title").value("Julius Caesar")
});
JcNode movie = new JcNode("movie");
JcNode actor = new JcNode("actor");
// construct the query
query = new JcQuery();
query.setClauses(new IClause[] {
MATCH.node(movie).label("Movie")
.property("title").value("The Matrix"),
MATCH.node(actor).label("Actor")
.relation().out().type("ACTS_IN").node(movie),
RETURN.value(actor),
RETURN.value(movie)
});
Previous | Next | Table of Contents |