Patterns
Cypher® relies on pattern matching to find data.
Patterns are often used in MATCH statements such as:
MATCH (m:Movie)<-[:ACTED_IN]-(a:Actor)
Patterns can be arbitrarily complex and @neo4j/cypher-builder provides the necessary tools to define them.
Nodes and relationships
A pattern is formed by nodes and relationships. The first step is to define the variables that are referenced in the pattern:
const person = new Cypher.Node();
const movie = new Cypher.Node();
const actedIn = new Cypher.Relationship();
Basic patterns
All patterns begin and end with a node. By using the variables defined previously, the pattern can be defined as follows:
const pattern = new Cypher.Pattern(person, { labels: ["Person"] }).related(actedIn, { type: "ACTED_IN" }).to(movie, { labels: ["Movie"] });
This pattern can now be used anywhere where a pattern can be used, for example, in a MATCH clause:
new Cypher.Match(pattern);
MATCH (this0:Person)-[this1:ACTED_IN]->(this2:Movie)
Relationship direction
By default, a relationship in a pattern is created as a left-to-right pattern.
The direction can be changed when defining the relationship in the pattern using the property direction:
const pattern = new Cypher.Pattern(person)
.related(actedIn, { type: "ACTED_IN", direction: "left" })
.to(movie);
This translates to the following pattern:
(this0)<-[this1:ACTED_IN]-(this2)
The options for direction are:
-
right(default): a left-to-right (()-[]→()) pattern. -
left: a right-to-left (()←[]-()) pattern. -
undirected: an undirected (()-[]-()) pattern.
Remove variable names
Variables for nodes and relationships are optional in the pattern, by not passing a variable it will not be rendered in Cypher:
const pattern = new Cypher.Pattern(person, { labels: ["Person"] }).related({type: "ACTED_IN"}).to({labels: ["Movie"]});
This translates to:
(this0:Person)-[:ACTED_IN]->(:Movie)
Remove labels and types
Labels and types are optional in the pattern:
const pattern = new Cypher.Pattern(person).related(actedIn).to(movie, { labels: ["Movie"] });
(this0)-[this1]->(this1:Movie)
|
Labels and types can be also defined in |
Properties
Patterns may contain properties to match both nodes and relationships.
These can be added using the properties argument:
const pattern = new Cypher.Pattern(person, {
labels: ["Person"],
properties: { name: new Cypher.Param("Person") },
}).related(actedIn, { type: "ACTED_IN" })
.to(movie, { labels: ["Movie"] });
(this0:Person { name: $param0 })-[this1:ACTED_IN]->(this2:Movie)
The properties argument takes an object with the properties to match and the param objects to be used as the expected values of the pattern.
It can be used in both node and relationship elements.
Advanced patterns
This section shows how to define more complex patterns.
Longer patterns
Patterns can be arbitrarily long. For example:
const user = new Cypher.Node();
const pattern = new Cypher.Pattern({labels: ["Person"]}).related({type: "ACTED_IN"}).to().related({direction: "left"}).to(user, { labels: ["User"] });
(:Person)-[:ACTED_IN]->()<-[]-(this1:User)
Cycles
A pattern may have cycles. To achieve this, you can reuse the same variables:
const actor = new Cypher.Node();
const movie = new Cypher.Node();
const actedIn = new Cypher.Relationship();
const directed = new Cypher.Relationship();
const pattern = new Cypher.Pattern(person, { labels: ["Person"] })
.related(actedIn, { type: "ACTED_IN" })
.to(movie, { labels: ["Movie"] })
.related(directed, { direction: "undirected", type: "DIRECTED" })
.to(actor);
This translates to:
(this0:Person)-[this1:ACTED_IN]->(this2:Movie)-[this3:DIRECTED]-(this0)
Note how the initial node in the pattern (this0) is the same as the one referenced in the last element.
This matches actors who also directed the same movie.
Length
The length (or hops) of a relationship can be defined with the length property.
Exact length
The exact length can be defined by passing a number:
const pattern = new Cypher.Pattern({}).related(actedIn, { type: "ACTED_IN", length: 3 }).to();
MATCH ()-[this1:ACTED_IN*3]->()
Min and max length
Bounds can be optionally added by passing an object with the following options:
-
min: defines the minimum length of the relationship. -
max: defines the maximum length of the relationship.
For example:
const pattern = new Cypher.Pattern({}).related(actedIn, { type: "ACTED_IN", length: {min: 2, max: 10} }).to();
MATCH ()-[this1:ACTED_IN*2..10]->()
WHERE predicates
WHERE clauses can be used as predicates for both nodes and relationships in the pattern:
const movie = new Cypher.Node({ labels: ["Movie"] });
new Cypher.Pattern(movie, { labels: ["Movie"] }).where(Cypher.eq(movie.property("title"), new Cypher.Literal("The Matrix")));
(this0:Movie WHERE this0.title = "The Matrix")
Label expressions
Complex label filters can be added as part of the Pattern by using Label Expressions. The following expressions are available:
-
labelExpr.and -
labelExpr.or -
labelExpr.not -
labelExpr.wildcard
For example:
const movieNode = new Cypher.Node();
const matchQuery = new Cypher.Match(
new Cypher.Pattern(movieNode, {
labels: Cypher.labelExpr.and(Cypher.labelExpr.or("Movie", "Film"), Cypher.labelExpr.not("Show")),
})
).return(movieNode);
MATCH (this0:((Movie|Film)&!Show))
RETURN this0
|
By default, multiple labels in a Pattern are combined with
|
Dynamic labels and types
Labels and relationship types in patterns can be dynamic expressions. To make labels dynamic, simply pass any Cypher expressions to labels or type. This will wrap the expression on $().
Example 1: Using a parameter as a node label
const query = new Cypher.Match(
new Cypher.Pattern(new Cypher.Node(), {
labels: new Cypher.Param("Movie"),
})
)
MATCH (this0:$($param0))
Example 2: Dynamic type on relationship
Same for relationship types:
const query = new Cypher.Match(
new Cypher.Pattern()
.related({
type: new Cypher.Param("ACTED_IN"),
})
.to()
)
MATCH ()-[:$($param1)]->()
Example 3: Using label expressions with dynamic labels
const movieLabel = new Cypher.Variable();
const query = new Cypher.With([new Cypher.Literal("Movie"), movieLabel])
.match(
new Cypher.Pattern(new Cypher.Node(), {
labels: Cypher.labelExpr.or(
movieLabel,
Cypher.labelExpr.not(new Cypher.Param("paramLabel"))
),
})
)
WITH "Movie" AS var0
MATCH (this1:($(var0)|!$($param1)))
Quantified path patterns
Quantified path patterns can be defined with the class Cypher.QuantifiedPath.
A QuantifiedPath represents an union of quantified or normal patterns. For example:
const m = new Cypher.Node();
const m2 = new Cypher.Node();
const quantifiedPath = new Cypher.QuantifiedPath(
new Cypher.Pattern(m, { labels: ["Movie"], properties: { title: new Cypher.Param("V for Vendetta") } }),
new Cypher.Pattern({ labels: ["Movie"] })
.related({ type: "ACTED_IN" })
.to({ labels: ["Person"] })
.quantifier({ min: 1, max: 2 }),
new Cypher.Pattern(m2, {
labels: ["Movie"],
properties: { title: new Cypher.Param("Something's Gotta Give") },
})
);
const query = new Cypher.Match(quantifiedPath).return(m2);
Generates the following Cypher:
MATCH (this0:Movie { title: $param0 })
((:Movie)-[:ACTED_IN]->(:Person)){1,2}
(this1:Movie { title: $param1 })
RETURN this1
Note that the QuantifiedPath requires at least one of the patterns to have a quantifier. This can be done with the .quantifier method of the normal Pattern class. This methods turns a pattern such as (:Movie)-[:ACTED_IN]→(:Person) into a quantified pattern: :Movie)-[:ACTED_IN]→(:Person{1,2}.
The method quantifier accepts a single parameter, which can be one of the following:
-
A number, defining the exact length.
-
An object with the optional properties
min,max. -
The strings
+or*.
Escaping labels and types
Labels and types will be automatically escaped if any uncommon character is detected. For example:
const movie = new Cypher.Node();
const match = new Cypher.Match(movie, { labels: ["My Movie"] }).return(movie);
MATCH (this0:`My Movie`)
RETURN this0
Note that My Movie is being surrounded in quotes to prevent code injection with dynamic labels.
Partial patterns
All patterns begin and end with a Node.
However, it is possible to define a partial pattern by using .related without .to:
const partialPattern = new Cypher.Pattern(person).related(actedIn);
In this case, the partial pattern cannot be used in any clause until it is completed with the .to method:
partialPattern.to(movie)