Migration to Cypher Builder 2

This page describes necessary steps and things to consider when migrating from Cypher® Builder 1.x to 2.0, as well as breaking changes that may affect projects using Cypher Builder.

The full, up to date changelog can be found in the Cypher® Builder changelog.

Patterns

Patterns have been reworked in version 1.x, and the old behavior have been removed in version 2.0.

Node shorthand removed from clauses

Clauses using patterns no longer accept a Cypher®.Node as a shorthand. An explicit pattern must be provided:

Before:

const movieNode = new Cypher.Node();

const matchQuery = new Cypher.Match(movieNode)

Now:

const movieNode = new Cypher.Node();

const matchQuery = new Cypher.Match(
    new Cypher.Pattern(movieNode)
);

This affects all clauses using patterns:

  • Cypher®.Match

  • Cypher®.Create

  • Cypher®.Merge

This also affects pattern comprehensions Cypher®.PatternComprehension.

Patterns no longer create a variable by default

Creating a pattern without options no longer adds variables to the generated Cypher®:

const pattern = new Cypher.Pattern()

Before:

(this0)

Now:

()

To add a variable, it must be passed when creating the pattern:

const movieNode = new Cypher.Node();
const pattern = new Cypher.Pattern(movieNode)

.getVariables

The method .getVariables has been removed from patterns. Since patterns no longer create variables, getVariables is no longer useful.

Patterns configuration

The following methods to configure the resulting pattern have been removed from Pattern:

  • .withoutLabels

  • .withoutVariable

  • .withProperties

  • .withVariables

  • .withoutType

  • .withDirection

  • .withLength

Instead, Patterns are now configured through objects in the constructor, as well as the related and to methods:

Before:

const a = new Cypher.Node({
    labels: ["Person", "Actor"],
});

const aProperties = {
    name: new Cypher.Param("Arthur"),
    surname: new Cypher.Param("Dent"),
};
const b = new Cypher.Node();
const rel = new Cypher.Relationship({
    type: "ACTED_IN",
});

new Cypher.Pattern(a)
    .withProperties({a: new Cypher.Param("Example")})
    .withoutVariable()
    .related(rel)
    .to(b)

Now:

const aProperties = {
    name: new Cypher.Param("Arthur"),
    surname: new Cypher.Param("Dent"),
};
const b = new Cypher.Node();
const rel = new Cypher.Relationship();


new Cypher.Pattern({ properties: aProperties, labels: ["Person", "Actor"] })
    .related(rel, { type: "ACTED_IN"})
    .to(b)

The generated Cypher®:

(:Person:Actor { name: $param0, surname: $param1 })-[this1:ACTED_IN]->(this2)

Note that labels and types are now defined in the pattern, not in the Node and Relationship classes.

Assign to path variable

The method assignToPath has been removed in the following clauses:

  • Match

  • Merge

  • Create

Instead, the method assignTo in Patterns must be used:

Before:

const pathVariable = new Cypher.Cypher.PathVariable()
new Cypher.Match(pattern).assignToPath(pathVariable).return(pathVariable);

Now:

const pathVariable = new Cypher.Cypher.PathVariable()
new Cypher.Match(pattern.assignTo(pathVariable)).return(pathVariable);

The generated Cypher®:

MATCH p = ()-[]-()
RETURN p

Node and relationship variables

Cypher®.Node and Cypher.Relationship no longer hold any data about labels, or types, making them more similar to Cypher.Variable. To add labels or types, they must be passed to the Cypher®.Pattern instead of relying on Cypher.Node and Cypher.Relationship.

Before:

const a = new Cypher.Node({
    labels: ["Person", "Actor"],
});
const b = new Cypher.Node();
const rel = new Cypher.Relationship({
    type: "ACTED_IN",
});

new Cypher.Pattern(a)
    .related(rel)
    .to(b)

Now:

const a = new Cypher.Node();
const b = new Cypher.Node();
const rel = new Cypher.Relationship();


new Cypher.Pattern(a, { labels: ["Person", "Actor"] })
    .related(rel, { type: "ACTED_IN"})
    .to(b)

Path variables

The variables used for paths Cypher®.Path and Cypher.NamedPath have been removed in favor of the more accurate names: Cypher.PathVariable and Cypher.NamedPathVariable:

Renamed features

The following features where deprecated in favor of a different name with the same functionality. The deprecated features have been removed in version 2.0:

  • Cypher®.concat in favor of Cypher.utils.concat

  • pointDistance in favor of point.distance

  • Merge.onCreate in favor of Merge.onCreateSet

  • Call.innerWith in favor of Call.importWith

  • cdc namespace in favor of db.cdc

    • db.cdc.current

    • db.cdc.earliest

    • db.cdc.query

  • rTrim and lTrim in favor of rtrim and ltrim respectively

.build()

The options for .build() are now passed as a single object rather than parameters:

Before:

myClause.build(
    "another-this",
    { myParam: "hello"},
    {
        labelOperator: "&"
    }
);

Now:

myClause.build({
    prefix: "another-this",
    extraParams: {
        myParam: "hello",
    },
    labelOperator: "&",
});

All parameters are optional, and build can still be called without parameters.

Remove support for fine-grained prefix

The first parameter "prefix" for the .build method in 1.x supports passing an object with the parameters params and variables for fine-grained control of what prefix to use in different kinds of variables. This has been removed in 2.x, supporting only a string as global prefix:

No longer supported:

myClause.build({
    variable: "var_prefix_",
    params: "param_prefix_"
});

Instead, a single string can be used as prefix for both variables and parameters:

Now:

myClause.build({
    prefix: "my-custom-prefix"
});

With

The method .with no longer adds new columns to the existing clause. It always creates a new WITH statement instead. Use the method .addColumns to add extra columns.

Before:

const withQuery = new Cypher.With(node);
withQuery.with(node);
withQuery.with("*");

Now:

const withQuery = new Cypher.With(node);
withQuery.with(node)
withQuery.addColumns("*");

The generated Cypher®:

WITH this0
WITH *, this0

RawCypher

Cypher®.RawCypher has been removed in favor of Cypher.Raw.

Update callback parameter

Cypher®.Raw no longer exposes a Cypher.Environment variable. Instead, it provides an instance of Cypher®RawContext with a compile method to compile nested elements in custom cypher.

Before:

const releasedParam = new Cypher.Param(1999);
const rawCypher = new Cypher.Raw((env: Cypher.Environment) => {
    const releasedParamId = env.compile(releasedParam); // Gets the raw Cypher for the param

    const customCypher = `MATCH(n) WHERE n.title=$title_param AND n.released=${releasedParamId}`;

    return customCypher;
});

Now:

const releasedParam = new Cypher.Param(1999);
const rawCypher = new Cypher.Raw((ctx: Cypher.RawCypherContext) => {
    const releasedParamId = ctx.compile(releasedParam); // Gets the raw Cypher for the param

    const customCypher = `MATCH(n) WHERE n.title=$title_param AND n.released=${releasedParamId}`;

    return customCypher;
});

Note that the code itself has not changed, and just the type passed to Cypher®.Raw callback has been changed from Cypher.Environment to Cypher.RawCypherContext.

Removed the utils.compileCypher

The utility function compileCypher® has been removed, in favor of using CypherRawContext.compile, which offers the same functionality.

PatternComprehension

PatternComprehension no longer accept a node as an argument in the constructor. Instead, use a pattern:

Before:

const node = new Cypher.Node();
const comprehension = new Cypher.PatternComprehension(node);

Now:

const node = new Cypher.Node();
const comprehension = new Cypher.PatternComprehension(new Cypher.Pattern(node));

.map

PatternComprehension no longer accepts a second argument for the map expression. Instead, use the .map method:

Before:

const andExpr = Cypher.eq(node.property("released"), new Cypher.Param(1999));
https://github.com/neo4j/cypher-builder/blob/main/CHANGELOG.md
const comprehension = new Cypher.PatternComprehension(new Cypher.Pattern(node), andExpr)

Now:

const andExpr = Cypher.eq(node.property("released"), new Cypher.Param(1999));

const comprehension = new Cypher.PatternComprehension(new Cypher.Pattern(node)).map(andExpr);

Other breaking changes

These are breaking changes that may affect the behavior of projects when updating to Cypher® Builder 2.0.

Fix TypeScript typings for boolean operators

The typings for the following boolean operators have been fixed to better reflect the result of these functions when spread parameters are used:

  • Cypher®.and

  • Cypher®.or

  • Cypher®.xor

The following now returns the correct type Cypher®.Predicate | undefined.:

const predicates: Cypher.Predicate[] = [];
const andPredicate = Cypher.and(...predicates);

This change means that additional checks may be needed when using boolean operators:

const predicates = [Cypher.true, Cypher.false];
const andPredicate = Cypher.and(...predicates); // type Cypher.Predicate | undefined

Passing parameters without spread still returns a defined type.

Literals escaping

Cypher®.Literal now escapes strings if they contain invalid characters. This avoids code injection.

new Cypher.Literal(`Hello "World"`);

Before:

"Hello "World""

Now:

"Hello \"World\""

Note that Cypher®.Param is still preferred over Cypher.Literal for dynamic values.