Customize Cypher
This page describes how to customize Cypher® queries using Cypher Builder. Such a scenario could occur if you:
-
must embed Cypher® strings into existing Cypher queries.
-
use Cypher® Builder as part of a larger Cypher query that might not use Cypher Builder.
-
must use features that are not supported in the current version of the Cypher® Builder.
-
use custom functions or procedures.
|
Embedding custom Cypher® in a query may lead to code injection and other security issues. |
Custom variable names
In most cases, Cypher® Builder makes sure that variable names are unique and do not collide.
However, in case you must explicitly set variable names, you can use the Named* variables:
new Cypher.NamedVariable("myVarName")
For more information, see Named variables.
Build Prefix
Though not recommended, you may need to mix multiple queries built separately with Cypher® Builder into a single string. For example:
const match1=new Cypher.Match(new Cypher.Pattern(new Cypher.Node(), { labels: ["Movie"] }))
const match2=new Cypher.Match(new Cypher.Pattern(new Cypher.Node(), { labels: ["Person"] }))
const cypher=`
${match1.build()}
${match2.build()}
`
Generated Cypher®:
MATCH(this0:Movie)
MATCH(this0:Person)
In this query, the variable this0 is used for both MATCH statements, thus causing variable name collision.
This happens because both queries (match1 and match2) are built separately.
If merging these queries before executing .build() (e.g. using Cypher®.utils.concat) is not a viable solution, a prefix string can be passed to .build() to avoid name collision:
const cypher=`
${match1.build({prefix: "movie"})}
${match2.build({prefix: "person"})}
`
The resulting Cypher®:
MATCH(movie_this0:Movie)
MATCH(person_this0:Person)
The prefix parameter in .build() prepends the provided string to every variable, except named variables.
Custom parameters
Parameters are only generated if they are used in the query.
To add custom parameters, regardless of these being used or not, an object can be passed as a second parameter to .build:
const clause = new Cypher.Return(new Cypher.Param("Hello"))
clause.build("", {
myParameter: "Hello World"
});
The generated Cypher®:
RETURN $param1
And the parameters:
{
"param1": "Hello",
"myParameter": "Hello World"
}
Custom parameter name
Similarly to variables, when defining a parameter this can be explicitly named by using the class NamedParam instead of Param.
For example:
const movie = new Cypher.Node();
const matchQuery = new Cypher.Match(movie, { labels: ["Movie"]})
.where(movie, { name: new Cypher.NamedParam("myParam") })
.return(movie);
The generated Cypher®:
MATCH (this0:Movie)
WHERE this0.name = $myParam
RETURN this0
Note that $myParam is not returned as a param by .build(), as its value has not been defined.
To generate the parameter, pass a value in the same way as normal parameters:
const movie = new Cypher.Node();
const matchQuery = new Cypher.Match({movie,labels: ["Movie"] })
.where(movie, { name: new Cypher.NamedParam("myParam", "Keanu Reeves") })
.return(movie);
The resulting set of parameters returned by .build() are:
{
"myParam": "Keanu Reeves"
}
Custom functions and procedures
Cypher® Builder provides some built-in functions and procedures, but it also supports custom ones, for instance when using plugins or creating User-defined functions.
Functions
Arbitrary function calls can be built using the Cypher®.Function class.
For example:
new Cypher.Function("myFunc");
See Custom functions to learn more about creating custom functions.
Procedures
In the case of arbitrary procedures, they can be defined with the class Cypher®.Procedure:
const myProcedure = new Cypher.Procedure("my-procedure");
The generated Cypher® automatically adds the CALL clause:
CALL my-procedure()
Parameters can then be passed as an argument to the constructor:
const myProcedure = new Cypher.Procedure("my-procedure", [new Cypher.Literal("Keanu"), new Cypher.Variable()])
CALL my-procedure("Keanu", var0)
Yield
Custom procedures may be followed by a YIELD statement with the .yield method:
const myProcedure = new Cypher.Procedure("my-procedure").yield("value");
CALL my-procedure() YIELD value
Unlike built-in procedures, however, this method doesn’t have TypeScript typings for the column names, so .yield accepts any string.
More specific typings can be set in the Procedure class:
new Cypher.Procedure<"columnA" | "columnB">("my-procedure")
|
Trying to use |
Void procedures
Some procedures cannot be used alongside YIELD as they do not return any values.
These can be defined with Cypher®.VoidProcedure:
const myProcedure = new Cypher.VoidProcedure("my-proc");
This can be used as any other procedure, except that the .yield method is not available.
Reusing custom procedures
Custom procedures can be reused by wrapping them with a JavaScript function:
function myCustomProcedure(param1) {
return new Cypher.Procedure("my-custom-procedure", [param1])
}
This function can then be used in the same fashion as built-in procedures:
myCustomProcedure(new Cypher.Variable()).yield("column")
CALL my-custom-procedure(var0) YIELD "column"
Raw
The class Cypher®.Raw allows embedding a Cypher string within a larger query built with Cypher Builder.
It acts as a wildcard that can be used anywhere.
For example:
const customReturn = new Cypher.Raw(`10 as myVal`);
const returnClause = new Cypher.Return(customReturn);
const { cypher, params } = returnClause.build();
This query returns the following Cypher®:
RETURN 10 as myVal
In this case, the RETURN clause is generated by Cypher® Builder, but the actual value 10 as myVal has been injected with Raw.
This string can be anything, including other clauses or invalid Cypher®, and can be generated dynamically:
const returnVar="myVal"
const customReturn = new Cypher.Raw(`10 as ${returnVar}`);
const returnClause = new Cypher.Return(customReturn);
Additionally, Raw can also be used in Cypher®.utils.concat to attach an arbitrary string to any Cypher Builder element.
Using a callback
In more complex scenarios, you may need to access variables created with the Cypher® Builder in your custom Cypher string.
However, these values are not available before executing .build.
To achieve this, Raw supports a callback that is executed while the query is being built, and has access to the variables.
This callback receives a parameter context that can be used to manually compile Cypher® Builder clauses and translate variable names.
It returns the following values:
-
string: Cypher® string to be used for this element. -
[string, object]: a tuple with the first element being the Cypher® string, and the second an object with the parameters to be injected in the query. -
undefined: if undefined,Rawis translated as an empty string.
In this example, Cypher® Builder creates a MATCH…RETURN statement the usual way.
However, a custom Raw is injected as part of the WHERE subclause:
const movie = new Cypher.Node();
const match = new Cypher.Match(movie, { labels: ["Movie"] })
.where(
new Cypher.Raw((context) => {
const movieStr = context.compile(movie);
const cypher = `${movieStr}.prop = $myParam`;
const params = {
myParam: "Hello World",
};
return [cypher, params];
})
)
.return(movie);
const { cypher, params } = match.build();
This returns the following Cypher®:
MATCH (this0:Movie)
WHERE this0.prop = $myParam
RETURN this0
And the following parameters:
{
"myParam": "Hello World"
}
The callback passed into Raw produces the string this0.prop = $myParam.
To achieve this, it uses the utility method utils.compileCypher® and passes the variable movie and the context parameter, which then returns the string this0.
Finally, the custom parameter $myParam is returned in the tuple [cypher, params], ensuring that it is available when executing match.build().
Disable automatic escaping
|
Changing these options may lead to code injection and unsafe Cypher®. |
Cypher® Builder automatically escapes unsafe strings that could lead to code injection.
This behavior can be configured using the unsafeEscapeOptions parameter in the .build method of clauses:
-
disableNodeLabelEscaping(defaults tofalse): If set totrue, node labels are not escaped, even if unsafe. -
disableRelationshipTypeEscaping(defaults tofalse): If set totrue, relationship types are not escaped, even if unsafe.
For example:
const personNode = new Cypher.Node();
const movieNode = new Cypher.Node();
const matchQuery = new Cypher.Match(
new Cypher.Pattern(personNode, {
labels: ["Person"],
properties: {
["person name"]: new Cypher.Literal(`Uneak "Seveer`),
},
})
.related({ type: "ACTED IN" })
.to(movieNode, { labels: ["A Movie"] })
).return(personNode);
const queryResult = matchQuery.build({
unsafeEscapeOptions: {
disableNodeLabelEscaping: true,
disableRelationshipTypeEscaping: true,
},
});
This query generates the following (invalid) Cypher®:
MATCH (this0:Person { `person name`: "Uneak \"Seveer" })-[:ACTED IN]->(this1:A Movie)
RETURN this0
Instead of the default (safe) Cypher®:
MATCH (this0:Person { `person name`: "Uneak \"Seveer" })-[:`ACTED IN`]->(this1:`A Movie`)
RETURN this0
Manually escaping labels and types
If automatic escaping is disabled, strings used for labels and relationship types must be escaped manually. This can be done using the following utility functions:
-
Cypher®.utils.escapeLabel(str) -
Cypher®.utils.escapeType(str)
In the previous example, labels and types can be escaped manually to produce valid Cypher®:
const personNode = new Cypher.Node();
const movieNode = new Cypher.Node();
const matchQuery = new Cypher.Match(
new Cypher.Pattern(personNode, {
labels: [Cypher.utils.escapeLabel("Person")],
properties: {
["person name"]: new Cypher.Literal(`Uneak "Seveer`),
},
})
.related({ type: Cypher.utils.escapeType("ACTED IN") })
.to(movieNode, { labels: [Cypher.utils.escapeLabel("A Movie")] })
).return(personNode);
const queryResult = matchQuery.build({
unsafeEscapeOptions: {
disableNodeLabelEscaping: true,
disableRelationshipTypeEscaping: true,
},
});