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 Cypher.Node and Cypher.Relationship, this behavior is deprecated and will be removed in the future

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 optinally 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]->()

Any length

By using the string "*", a relationship with any length will be matched:

const pattern = new Cypher.Pattern({}).related(actedIn, { type: "ACTED_IN", length: "*" }).to();
MATCH ()-[this1:ACTED_IN*]->()

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")

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)