© 2020 Neo4j, Inc.,
This is the Neo4j Cypher-DSL manual version 2024.2.0.
Who should read this?
This manual is written for people interested in creating Cypher queries in a typesafe way on the JVM.
All public classes inside org.neo4j.cypherdsl still subject to breaking changes are annotated with @API(status = EXPERIMENTAL) .
|
1. Introduction
1.1. Purpose
The Cypher-DSL has been developed with the needs of Spring Data Neo4j. We wanted to avoid string concatenations in our query generation and decided do go with a builder approach, much like we find with jOOQ or in the relational module of Spring Data JDBC, but for Cypher.
What we don’t have - and don’t need for our mapping purpose - at the moment is a code generator that reads the database schema and generates static classes representing labels and relationship types. That is still up to the mapping framework (in our case SDN). We however have a type safe API for Cypher that allows only generating valid Cypher constructs.
We worked closely with the OpenCypher spec here and you find a lot of these concepts in the API.
The Cypher-DSL can also be seen in the same area as the Criteria API of Spring Data Mongo.
1.2. Where to use it
The Cypher-DSL creates an Abstract Syntax Tree (AST) representing your Cypher-Statements.
An instance of a org.neo4j.cypherdsl.core.Statement
representing that AST is provided at the end of query building step.
A Renderer
is then used to create literal Java-Strings.
Those can be used in any context supporting String-based queries, for example with the Neo4j Java driver or inside embedded procedures and of course with Spring Data’s Neo4j-Client.
Parameters in the generated queries will use the $form
and as such be compatible with all current versions of Neo4j.
Users of SDN 6+ can use the generated org.neo4j.cypherdsl.core.Statement
directly with the Neo4jTemplate
or the ReactiveNeo4jTemplate
.
Both the imperative and the reactive variants allow the retrieval and counting of entities without rendering a String first,
for example through Neo4jTemplate#findAll(Statement, Class<T>)
.
1.3. Java API
Find the Java-API and a generated project info here: API and project info.
2. Getting started
2.1. Prepare dependencies
Please use a dependency management system. We recommend either Maven or Gradle.
2.2. How to use it
You use the Cypher-DSL as you would write Cypher: it allows to write down even complex Cypher queries from top to bottom in a type safe, compile time checked way.
The examples to follow are using JDK 11.
We find the var
keyword especially appealing in such a DSL as the types returned by the DSL are much less important than
the further building methods they offer.
The AST parts and intermediate build steps are immutable. That is, the methods create new intermediate steps.
For example, you cannot reuse an ExposesLimit step, but have to use the returned object from its skip method.
|
An instance of a org.neo4j.cypherdsl.core.Statement
is provided at the end of every query building step.
This Statement
needs to be rendered into a string or passed to methods supporting it as input.
Please get an instance of the default renderer via org.neo4j.cypherdsl.renderer.Renderer#getDefaultRenderer()
.
The renderer provides a single method render
for rendering the AST into a string representation.
Furthermore, the Statement
will collect parameter names and if provided, parameter values.
Parameter names and values are available after the statement has been built and can for example be used directly with
Neo4j-Java-Driver.
2.2.1. Examples
The following examples are 1:1 copies of the queries you will find in the Neo4j browser after running :play movies
.
They use the following imports:
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collection;
import java.util.Locale;
import java.util.Map;
import org.junit.jupiter.api.Test;
import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.SortItem;
import org.neo4j.cypherdsl.core.Statement;
import org.neo4j.cypherdsl.core.SymbolicName;
import org.neo4j.cypherdsl.core.renderer.Configuration;
import org.neo4j.cypherdsl.core.renderer.Renderer;
To match and return all the movie, build your statement like this:
var m = Cypher.node("Movie").named("m"); (1)
var statement = Cypher.match(m) (2)
.returning(m)
.build(); (3)
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (m:`Movie`) RETURN m");
1 | Declare a variable storing your node labeled Movie and named m , so that you can |
2 | reuse it in both the match and the return part. |
3 | The build method becomes only available when a compilable Cypher statement can be rendered. |
Find
Match all nodes with a given set of properties:
var tom = Cypher.anyNode().named("tom").withProperties("name", Cypher.literalOf("Tom Hanks"));
var statement = Cypher
.match(tom).returning(tom)
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (tom {name: 'Tom Hanks'}) RETURN tom");
Limit the number of returned things and return only one attribute
var people = Cypher.node("Person").named("people");
statement = Cypher
.match(people)
.returning(people.property("name"))
.limit(10)
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (people:`Person`) RETURN people.name LIMIT 10");
Create complex conditions
var nineties = Cypher.node("Movie").named("nineties");
var released = nineties.property("released");
statement = Cypher
.match(nineties)
.where(released.gte(Cypher.literalOf(1990)).and(released.lt(Cypher.literalOf(2000))))
.returning(nineties.property("title"))
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo(
"MATCH (nineties:`Movie`) WHERE (nineties.released >= 1990 AND nineties.released < 2000) RETURN nineties.title");
Query
Build relationships
var tom = Cypher.node("Person").named("tom").withProperties("name", Cypher.literalOf("Tom Hanks"));
var tomHanksMovies = Cypher.anyNode("tomHanksMovies");
var statement = Cypher
.match(tom.relationshipTo(tomHanksMovies, "ACTED_IN"))
.returning(tom, tomHanksMovies)
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo(
"MATCH (tom:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(tomHanksMovies) RETURN tom, tomHanksMovies");
var cloudAtlas = Cypher.anyNode("cloudAtlas").withProperties("title", Cypher.literalOf("Cloud Atlas"));
var directors = Cypher.anyNode("directors");
statement = Cypher
.match(cloudAtlas.relationshipFrom(directors, "DIRECTED"))
.returning(directors.property("name"))
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (cloudAtlas {title: 'Cloud Atlas'})<-[:`DIRECTED`]-(directors) RETURN directors.name");
tom = Cypher.node("Person").named("tom").withProperties("name", Cypher.literalOf("Tom Hanks"));
var movie = Cypher.anyNode("m");
var coActors = Cypher.anyNode("coActors");
var people = Cypher.node("Person").named("people");
statement = Cypher
.match(tom.relationshipTo(movie, "ACTED_IN").relationshipFrom(coActors, "ACTED_IN"))
.returning(coActors.property("name"))
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo(
"MATCH (tom:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(m)<-[:`ACTED_IN`]-(coActors) RETURN coActors.name");
cloudAtlas = Cypher.node("Movie").withProperties("title", Cypher.literalOf("Cloud Atlas"));
people = Cypher.node("Person").named("people");
var relatedTo = people.relationshipBetween(cloudAtlas).named("relatedTo");
statement = Cypher
.match(relatedTo)
.returning(people.property("name"), Cypher.type(relatedTo), relatedTo.getRequiredSymbolicName())
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo(
"MATCH (people:`Person`)-[relatedTo]-(:`Movie` {title: 'Cloud Atlas'}) RETURN people.name, type(relatedTo), relatedTo");
Solve
var bacon = Cypher.node("Person").named("bacon").withProperties("name", Cypher.literalOf("Kevin Bacon"));
var hollywood = Cypher.anyNode("hollywood");
var statement = Cypher
.match(bacon.relationshipBetween(hollywood).length(1, 4))
.returningDistinct(hollywood)
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (bacon:`Person` {name: 'Kevin Bacon'})-[*1..4]-(hollywood) RETURN DISTINCT hollywood");
Recommend
var tom = Cypher.node("Person").named("tom").withProperties("name", Cypher.literalOf("Tom Hanks"));
var coActors = Cypher.anyNode("coActors");
var cocoActors = Cypher.anyNode("cocoActors");
var strength = Cypher.count(Cypher.asterisk()).as("Strength");
var statement = Cypher
.match(
tom.relationshipTo(Cypher.anyNode("m"), "ACTED_IN").relationshipFrom(coActors, "ACTED_IN"),
coActors.relationshipTo(Cypher.anyNode("m2"), "ACTED_IN").relationshipFrom(cocoActors, "ACTED_IN")
)
.where(
Cypher.not(tom.relationshipTo(Cypher.anyNode(), "ACTED_IN").relationshipFrom(cocoActors, "ACTED_IN")))
.and(tom.isNotEqualTo(cocoActors))
.returning(
cocoActors.property("name").as("Recommended"),
strength
).orderBy(strength.asName().descending())
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo(""
+ "MATCH "
+ "(tom:`Person` {name: 'Tom Hanks'})-[:`ACTED_IN`]->(m)<-[:`ACTED_IN`]-(coActors), "
+ "(coActors)-[:`ACTED_IN`]->(m2)<-[:`ACTED_IN`]-(cocoActors) "
+ "WHERE (NOT (tom)-[:`ACTED_IN`]->()<-[:`ACTED_IN`]-(cocoActors) AND tom <> cocoActors) "
+ "RETURN cocoActors.name AS Recommended, count(*) AS Strength ORDER BY Strength DESC");
2.2.2. More features
Retrieving parameters being defined
A placeholder for a parameter can be defined via Cypher.parameter("param")
.
This placeholder will be rendered as $param
and must be filled with the appropriate means of the environment you’re working with.
In addition, an arbitrary value can be bound to the name via Cypher.parameter("param", "a value")
or Cypher.parameter("param").withValue("a value")
.
NULL
is a valid value.
The Cypher-DSL will not use those values, but collect them for you.
The following example shows how to access them and how to use it:
var person = Cypher.node("Person").named("p");
var statement =
Cypher
.match(person)
.where(person.property("nickname").isEqualTo(Cypher.parameter("nickname")))
.set(
person.property("firstName").to(Cypher.parameter("firstName").withValue("Thomas")),
person.property("name").to(Cypher.parameter("name", "Anderson"))
)
.returning(person)
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (p:`Person`) WHERE p.nickname = $nickname SET p.firstName = $firstName, p.name = $name RETURN p");
Collection<String> parameterNames = statement
.getCatalog()
.getParameterNames();
assertThat(parameterNames).containsExactlyInAnyOrder("nickname", "firstName", "name"); (1)
Map<String, Object> parameters = statement
.getCatalog()
.getParameters();
assertThat(parameters).hasSize(2); (2)
assertThat(parameters)
.containsEntry("firstName", "Thomas")
.containsEntry("name", "Anderson");
1 | The names contain all placeholders, also those without a value |
2 | The parameter map contains only parameters with defined values |
If you define a parameter with conflicting values, a ConflictingParametersException
will be thrown the moment you try to retrieve the collected parameters.
Using the default renderer
A statement can render itself as well:
var statement = Cypher.returning(literalTrue().as("t")).build();
var cypher = statement.getCypher();
assertThat(cypher).isEqualTo("RETURN true AS t");
This, together with the above, makes the statement a complete accessor for a Cypher-statement and its parameters.
Retrieving identifiable expressions
The Statement
as well as the intermediate build steps after defining a WITH
or RETURN
clause allow to retrieve identifiable expressions
via getIdentifiableExpressions()
. All expressions identifiable via a name such as named nodes and relationships, symbolic names or aliased
expressions are included. In addition, properties are also available.
Those information can be used when dynamically building a query to verify the presence of required expressions or use them for further refinement.
The feature plays well with the driver integration and could be directly used to define mapping functions.
Statements parsed via the optional parser module are also able to return their identifiable expressions. The use case here might be evaluating a statement defined by a user being parsed and then checked if everything required is returned.
Generating formatted Cypher
The Cypher-DSL can also format the generated Cypher to some extend.
The Renderer
offers the overload Renderer getRenderer(Configuration configuration)
, taking in an instance of org.neo4j.cypherdsl.core.renderer.Configuration
.
Instances of Configuration
are thread-safe and reusable.
The class offers a couple of static convenience methods for retrieving some variants.
var n = Cypher.anyNode("n");
var a = Cypher.node("A").named("a");
var b = Cypher.node("B").named("b");
var mergeStatement = Cypher.merge(n)
.onCreate().set(n.property("prop").to(Cypher.literalOf(0)))
.merge(a.relationshipBetween(b, "T"))
.onCreate().set(a.property("name").to(Cypher.literalOf("me")))
.onMatch().set(b.property("name").to(Cypher.literalOf("you")))
.returning(a.property("prop")).build();
var renderer = Renderer.getRenderer(Configuration.prettyPrinting()); (1)
assertThat(renderer.render(mergeStatement))
.isEqualTo(
"MERGE (n)\n" +
" ON CREATE SET n.prop = 0\n" +
"MERGE (a:A)-[:T]-(b:B)\n" +
" ON CREATE SET a.name = 'me'\n" +
" ON MATCH SET b.name = 'you'\n" +
"RETURN a.prop"
); (2)
1 | Get a "pretty printing" instance of the renderer configuration and retrieve a renderer based on it |
2 | Enjoy formatted Cypher. |
Escaping names
The default renderer in its default configuration will escape all names (labels and relationship types) by default.
So Movie
becomes `Movie`
and ACTED_IN
becomes `ACTED_IN`
.
If you don’t want this, you can either create a dedicated configuration for a renderer with that setting turned off or use
the pretty printing renderer:
var relationship = Cypher.node("Person").named("a")
.relationshipTo(Cypher.node("Movie").named("m"), "ACTED_IN").named("r");
var statement = Cypher.match(relationship).returning(relationship).build();
var defaultRenderer = Renderer.getDefaultRenderer();
assertThat(defaultRenderer.render(statement))
.isEqualTo("MATCH (a:`Person`)-[r:`ACTED_IN`]->(m:`Movie`) RETURN r");
var escapeOnlyIfNecessary = Configuration.newConfig().alwaysEscapeNames(false).build();
var renderer = Renderer.getRenderer(escapeOnlyIfNecessary);
assertThat(renderer.render(statement))
.isEqualTo("MATCH (a:Person)-[r:ACTED_IN]->(m:Movie) RETURN r");
renderer = Renderer.getRenderer(Configuration.prettyPrinting());
assertThat(renderer.render(statement))
.isEqualTo("MATCH (a:Person)-[r:ACTED_IN]->(m:Movie)\nRETURN r");
Inserting raw Cypher
Cypher.raw
allows for creating arbitrary expressions from raw String literals.
Users discretion is advised:
var key = Cypher.name("key");
var cypher = Cypher.call("apoc.meta.schema")
.yield("value").with("value")
.unwind(Cypher.keys(Cypher.name("value"))).as(key)
.returning(
key,
Cypher.raw("value[$E]", key).as("value") (1)
)
.build().getCypher();
assertThat(cypher).isEqualTo(
"CALL apoc.meta.schema() YIELD value WITH value UNWIND keys(value) AS key RETURN key, value[key] AS value");
1 | The Cypher-DSL doesn’t support a dynamic lookup of properties on expression at the moment.
We use a raw Cypher string with the placeholder $E which resolves to the symbolic name also passed to the raw function. |
2.3. Dialect support
There is limited dialect support. The default dialect org.neo4j.cypherdsl.core.renderer.Dialect.DEFAULT
is Neo4j 4.4 and earlier whereas org.neo4j.cypherdsl.core.renderer.Dialect.NEO4J_5
is designated to work with Neo4j 5.
The Neo4j 5 dialect will render some things differently:
-
org.neo4j.cypherdsl.core.Functions.distance
will be rendered aspoint.distance
instead of justdistance
-
elementId
will be rendered "as is" -
EXISTS(n.property)
will be rendered asn.property IS NOT NULL
Additional features might be added.
It is especially noteworthy that elementId(node) will be rendered as toString(id(node)) with the default dialect. This is helpful for building queries that must work a) without deprecation warnings and b) on both Neo4j 4 and 5. Of course this is not portable and the returned values must only be used for comparison inside one statement. But Neo4j advices strongly against making the internal entity ids available outside a transaction (See elementID: "Outside of the scope of a single transaction, no guarantees are given about the mapping between ID values and elements.")
|
Here is one example how to use a dialect:
import org.neo4j.cypherdsl.core.renderer.Configuration;
import org.neo4j.cypherdsl.core.renderer.Dialect;
import org.neo4j.cypherdsl.core.renderer.Renderer;
class DialectIT {
void shouldRenderElementId() {
var screen = Cypher.node("ScreenStateNode").named("screen");
var id = Cypher.literalOf("4:d32903f5-48ef-40fb-9ce5-9a3039852c46:2");
var statement = Cypher.match(screen)
.where(Cypher.elementId(screen).eq(id))
.returning(Cypher.elementId(screen))
.build();
// Config and renderer is thread safe, you can store it somewhere global
var rendererConfig = Configuration.newConfig().withDialect(Dialect.NEO4J_5).build();
var renderer = Renderer.getRenderer(rendererConfig);
var cypher = renderer.render(statement);
assertThat(cypher)
.isEqualTo(
"MATCH (screen:`ScreenStateNode`) " +
"WHERE elementId(screen) = '4:d32903f5-48ef-40fb-9ce5-9a3039852c46:2' " +
"RETURN elementId(screen)"
);
}
}
2.4. Catalog support
The Cypher-DSL offers a catalog view on statements. After a statement has been build, a catalog can be retrieved like this:
var p = Cypher.node("Person").named("p");
var m = Cypher.node("Movie").named("m");
var a = m.withProperties("title", Cypher.literalOf("The Matrix"))
.relationshipFrom(p, "ACTED_IN").named("a");
var statement = Cypher
.match(a)
.where(p.property("born").gte(Cypher.parameter("born", 1979)))
.returning(p)
.build();
var catalog = statement.getCatalog();
assertThat(catalog.getNodeLabels())
.extracting(StatementCatalog.Token::value)
.containsExactlyInAnyOrder("Person", "Movie");
assertThat(catalog.getProperties())
.containsExactlyInAnyOrder(
StatementCatalog.property(Set.of(StatementCatalog.label("Movie")), "title"),
StatementCatalog.property(Set.of(StatementCatalog.label("Person")), "born")
);
In the above example the catalog contains all labels and types used (Movie
, Person
, ACTED_IN
) and all conditions
that involves a property of an entity combining the labels or types into a node or relationship. In addition, the catalog
contains the identifiable elements.
This becomes especially powerful when combining with the optional parser module:
var input = """
MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`)
WHERE p.born >= $born
RETURN p
""";
var statement = CypherParser.parse(input);
var catalog = statement.getCatalog();
assertThat(catalog.getNodeLabels())
.extracting(StatementCatalog.Token::value)
.containsExactlyInAnyOrder("Person", "Movie");
assertThat(catalog.getProperties())
.containsExactlyInAnyOrder(
StatementCatalog.property(Set.of(StatementCatalog.label("Movie")), "title"),
StatementCatalog.property(Set.of(StatementCatalog.label("Person")), "born")
);
3. Properties
Nodes and Relationships expose properties. This reflects directly in the Cypher-DSL:
var personNode = Cypher.node("Person").named("p");
var movieNode = Cypher.node("Movie");
var ratedRel = personNode.relationshipTo(movieNode, "RATED").named("r");
var statement = Cypher.match(ratedRel)
.returning(
personNode.property("name"), (1)
ratedRel.property("rating")) (2)
.build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (p:`Person`)-[r:`RATED`]->(:`Movie`) RETURN p.name, r.rating");
1 | Create a property expression from a Node |
2 | Create a property expression from a Relationship |
Both Node and Relationship should be named as in the example. The Cypher-DSL generates names if they are not named, to refer to them in the statements. Without the explicit names, the generated statement would look like this:
MATCH (geIcWNUD000:`Person`)-[TqfqBNcc001:`RATED`]->(:`Movie`) RETURN geIcWNUD000.name, TqfqBNcc001.rating
The name is of course random.
The Cypher
class exposes the property
method, too. This methods takes in one name (as symbolic name or as string literal) OR one expression
and at least one further string, referring to the name of the property.
Passing in a symbolic name would lead to a similar result like in [properties-on-nodes-and-rel], an expression can refer to the results of functions etc.:
var epochSeconds = Cypher.property(Cypher.datetime(), "epochSeconds"); (1)
var statement = Cypher.returning(epochSeconds).build();
Assertions.assertThat(cypherRenderer.render(statement))
.isEqualTo("RETURN datetime().epochSeconds");
1 | Here an expression, the datetime() function is passed in and the epocheSeconds property is dereferenced. |
Nested properties are of course possible as well, either directly on nodes and relationships or via the static builder:
var node = Cypher.node("Person").named("p");
var locationPropV1 = Cypher.property(node.getRequiredSymbolicName(), "home.location", "y");
var locationPropV2 = Cypher.property("p", "home.location", "y");
var statement = Cypher.match(node)
.where(locationPropV1.gt(Cypher.literalOf(50)))
.returning(locationPropV2).build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("MATCH (p:`Person`) WHERE p.`home.location`.y > 50 RETURN p.`home.location`.y");
4. Functions
There are many more functions implemented in org.neo4j.cypherdsl.core.Functions . Not all of them are already documented here.
|
4.1. Lists
4.1.1. range()
Creates an invocation of range()
.
Given the following imports
import org.neo4j.cypherdsl.core.Cypher;
var range = Cypher.range(0, 10);
Gives you range(0,10)
.
The step size can be specified as well:
var range = Cypher.range(0, 10, 1);
This gives you range(0,10,1)
.
Both variants of range
also take a NumberLiteral
for the start and end index and the step size.
4.1.2. Inserting a list operator
The list operator []
selects a specific value of a list or a range of values from a list.
A range can either be closed or open.
Find examples to select a value at a given index, a sublist based on a closed range and sublist based on open ranges below. While the examples use Section 4.1.1, the list operator doesn’t put any restrictions on the expression at the moment.
@Test
void valueAtExample() {
var range = Cypher.range(0, 10);
var statement = Cypher.returning(Cypher.valueAt(range, 3)).build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("RETURN range(0, 10)[3]");
}
@Test
void subListUntilExample() {
var range = Cypher.range(Cypher.literalOf(0), Cypher.literalOf(10));
var statement = Cypher.returning(Cypher.subListUntil(range, 3)).build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("RETURN range(0, 10)[..3]");
}
@Test
void subListFromExample() {
var range = Cypher.range(0, 10, 1);
var statement = Cypher.returning(Cypher.subListFrom(range, -3)).build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("RETURN range(0, 10, 1)[-3..]");
}
@Test
void subListExample() {
var range = Cypher.range(Cypher.literalOf(0), Cypher.literalOf(10), Cypher.literalOf(1));
var statement = Cypher.returning(Cypher.subList(range, 2, 4)).build();
assertThat(cypherRenderer.render(statement))
.isEqualTo("RETURN range(0, 10, 1)[2..4]");
}
4.2. Mathematical functions
The Cypher-DSL supports all mathematical functions as of Neo4j 4.2. Please find their description in the Neo4j cypher manual:
4.3. Calling arbitrary procedures and functions
Neo4j has plethora of built-in procedures and functions.
The Cypher-DSL does cover a lot of them already, and they can be called in a typesafe way on many Expression
-instances
taking in various expressions such as the results of other calls, literals or parameters.
However, there will probably always be a gap between what the Cypher-DSL includes and what the Neo4j database brings. More so, there are fantastic libraries out there like APOC. APOC has so many procedures and functions in so many categories that it is rather futile to add shims for all of them consistently.
Probably the most important aspect of all: many Neo4j users bring their knowledge to the database themselves, in the form of their stored procedures. Those should be callable as well.
The Cypher-DSL is flexible enough to call all those procedures and functions.
4.3.1. Calling custom procedures
Procedures are called via the Cypher CALL-Clause. The CALL-Clause can appear as a StandAlone call and as an InQuery call. Both are supported by the Cypher-DSL.
Standalone procedure calls
Standalone calls are particular useful for VOID
procedures.
A VOID procedure is a procedure that does not declare any result fields and returns no result records and that has explicitly been declared as VOID
.
Let’s take the first example from here
var call = Cypher.call("db", "labels").build(); (1)
assertThat(cypherRenderer.render(call)).isEqualTo("CALL db.labels()");
call = Cypher.call("db.labels").build(); (2)
assertThat(cypherRenderer.render(call)).isEqualTo("CALL db.labels()");
1 | Cypher.call returns a buildable statement that can be rendered.
Cypher.call can be used with separate namespace and procedure name as shown here or |
2 | with a single argument equal to the name of the procedure. |
Of course, arguments can be specified as expressions.
Expressions can be literals as in [standalone-call-with-args], Cypher parameters (via Cypher.parameter
) that bind your input or nested calls:
var call = Cypher
.call("dbms.security.createUser")
.withArgs(Cypher.literalOf("johnsmith"), Cypher.literalOf("h6u4%kr"), Cypher.literalFalse())
.build();
assertThat(cypherRenderer.render(call))
.isEqualTo("CALL dbms.security.createUser('johnsmith', 'h6u4%kr', false)");
Last but not least, the Cypher-DSL can of course YIELD
the results from a standalone call:
var call = Cypher.call("dbms.procedures").yield("name", "signature").build(); (1)
assertThat(cypherRenderer.render(call)).isEqualTo("CALL dbms.procedures() YIELD name, signature");
call = Cypher.call("dbms.procedures").yield(Cypher.name("name"), Cypher.name("signature")).build(); (2)
assertThat(cypherRenderer.render(call)).isEqualTo("CALL dbms.procedures() YIELD name, signature");
1 | Yielded items can be specified via string⦠|
2 | β¦or with symbolic names created earlier |
A standalone call can spot a WHERE
clause as well:
var name = Cypher.name("name");
var call = Cypher
.call("dbms.listConfig")
.withArgs(Cypher.literalOf("browser"))
.yield(name)
.where(name.matches("browser\\.allow.*"))
.returning(Cypher.asterisk())
.build();
assertThat(cypherRenderer.render(call)).isEqualTo(
"CALL dbms.listConfig('browser') YIELD name WHERE name =~ 'browser\\\\.allow.*' RETURN *");
In-query procedure calls
In-query calls are only possible with non-void procedures. An In-query call happens inside the flow of a normal query. The mechanics to construct those calls via the Cypher-DSL are identical to standalone calls:
var label = Cypher.name("label");
var statement = Cypher
.match(Cypher.anyNode().named("n")).with("n")
.call("db.labels").yield(label).with(label)
.returning(Cypher.count(label).as("numLabels"))
.build();
assertThat(cypherRenderer.render(statement)).isEqualTo(
"MATCH (n) WITH n CALL db.labels() YIELD label WITH label RETURN count(label) AS numLabels");
A CALL
can be used after a MATCH
, a WITH
and also a WHERE
clause.
4.3.2. Use stored-procedure-calls as expressions (Calling custom functions)
All the mechanics described and shown above - how to define a custom call statement, supply it with arguments etc. - doesn’t distinguish between procedures and functions. Every stored procedure can be treated as a function - as long as the stored procedure returns a single value. It doesn’t matter if the single value returns a scalar or a list of objects. A list of objects is still a single value, in contrast to a stream of objects returned by a non-void procedure.
So the question is not how to call a stored custom function, but how to turn a call statement into an expression that can be used in any place in a query where an expression is valid.
This is where asFunction
comes in.
var p = Cypher.node("Person").named("p");
var createUuid = Cypher.call("apoc.create.uuid").asFunction(); (1)
var statement = Cypher.merge(p.withProperties(Cypher.mapOf("id", createUuid))) (2)
.set(
p.property("firstName").to(Cypher.literalOf("Michael")),
p.property("surname").to(Cypher.literalOf("Hunger"))
)
.returning(p)
.build();
assertThat(cypherRenderer.render(statement)).isEqualTo(
"MERGE (p:`Person` {id: apoc.create.uuid()}) "
+ "SET p.firstName = 'Michael', p.surname = 'Hunger' "
+ "RETURN p");
1 | First we define a call as seen earlier and turn it into an expression |
2 | This expression is than used as any other expression |
Of course, arguments to those functions can be expressed as well, either as literals or expressions.
var p = Cypher.node("Person").named("p");
var createUuid = Cypher.call("apoc.create.uuid").asFunction(); (1)
var toCamelCase = Cypher.call("apoc.text.camelCase")
.withArgs(Cypher.literalOf("first name")) (2)
.asFunction();
var statement = Cypher.merge(p.withProperties(Cypher.mapOf("id",
createUuid)))
.set(p.property("surname").to(Cypher.literalOf("Simons")))
.with(p)
.call("apoc.create.setProperty").withArgs(
p.getRequiredSymbolicName(),
toCamelCase,
Cypher.parameter("nameParam") (3)
).yield("node")
.returning("node")
.build();
assertThat(cypherRenderer.render(statement)).isEqualTo(
"MERGE (p:`Person` {id: apoc.create.uuid()}) SET p.surname = 'Simons' "
+ "WITH p CALL apoc.create.setProperty(p, apoc.text.camelCase('first name'), $nameParam) "
+ "YIELD node RETURN node");
1 | Same as before |
2 | A call to APOC’s camelCase function, taking in the literal of first name . |
3 | A call to another APOC function to which a parameter is passed. You find that corresponding placeholder as $nameParam in the following assert |
4.3.3. Summary
Through Cypher.call
any procedure or function can be called in case one of your favorite procedures is missing in org.neo4j.cypherdsl.core.Functions
.
All clauses, including YIELD
and WHERE
on procedures are supported.
All procedures can be turned into functions.
The Cypher-DSL however does not check if the procedure that is used as a function is actually eligible to do so.
If the Cypher-DSL misses an important builtin Neo4j function, please raise a ticket.
5. Parsing existing Cypher
5.1. Introduction
The cypher-dsl-parser
module ins an optional add-on to the Cypher-DSL that takes your existing Cypher - either whole statements
or fragments like clauses or expressions - and turn them into Cypher-DSL statements or expressions.
Those fragments can be used to add custom Cypher to your generated statements without resorting to raw String literals.
It allows sanitizing user input, add additional filters for labels and types to rewrite queries and more.
The parser itself is based on Neo4j’s official Cypher-Parser, thus supporting the same constructs as Neo4j itself.
However, while we could theoretically parse all expressions that Neo4j 5.24.0 supports, we might cannot translate all of them
into elements of the Cypher-DSL. In such cases an UnsupportedOperationException
will be thrown.
5.2. Getting started
5.2.1. Add additional dependencies
5.2.2. Minimum JDK version
The Cypher-Parser requires JDK 11 to run which is the same version that Neo4j 5.24.0 requires.
5.2.3. Main entry point
The main entry point to parsing Cypher strings into Cypher-DSL objects is
import org.neo4j.cypherdsl.parser.CypherParser;
It provides a list of static methods:
Method | What it does |
---|---|
|
Parses a pattern like |
|
Parses a pattern like |
|
Parses an arbitrary expression. |
|
Parses a full clause like |
|
Parses a whole statement. The result can be rendered or used in a union or subquery call. |
|
An alias for |
The README
for the parser module itself contains not only our whole TCK for the parser,
but also several examples of calling it. Have a look here: neo4j-cypher-dsl-parser/README.adoc.
All the methods mention above provide an overload taking in an additional org.neo4j.cypherdsl.parser.Option
instance
allowing to interact with the parser. Please have a look at the JavaAPI for information about the options class.
The following examples show some ways of using it.
Most of the configurable options represent ways to provide filters for labels or types or are callbacks when certain expressions are created.
5.3. Examples
5.3.1. Parsing user input and call in a subquery
This is helpful when you create an outer query that maybe enriched by a user. Here we assume the user does the right thing and don’t modify the query any further:
var userProvidedCypher
= "MATCH (this)-[:LINK]-(o:Other) RETURN o as result"; (1)
var userStatement = CypherParser.parse(userProvidedCypher); (2)
var node = Cypher.node("Node").named("node");
var result = Cypher.name("result");
var cypher = Cypher (3)
.match(node)
.call((4)
userStatement,
node.as("this")
)
.returning(result.project("foo", "bar"))
.build()
.getCypher();
assertThat(cypher).isEqualTo(
"MATCH (node:`Node`) "
+ "CALL {"
+ "WITH node "
+ "WITH node AS this " (5)
+ "MATCH (this)-[:`LINK`]-(o:`Other`) RETURN o AS result" (6)
+ "} "
+ "RETURN result{.foo, .bar}");
1 | A valid standalone query that is also a valid subquery |
2 | Just parse it into a Statement object |
3 | Use the Cypher-DSL as explained throughout the docs |
4 | Use the overload of call that takes a Statement and a collection of expression that should be imported into the subquery |
5 | Notice how to WITH clauses are generated: The first one is the importing one, the second one the aliasing one |
6 | This is the original query |
5.3.2. Ensure an alias for the return clause
We are going to work with the same test as in Listing 19, so this is not repeated. Here we make sure the query supplied by the user returns something with a required alias.
var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) RETURN o";
Function<Expression, AliasedExpression> ensureAlias = r -> {
if (!(r instanceof AliasedExpression)) {
return r.as("result");
}
return (AliasedExpression) r;
}; (1)
var options = Options.newOptions() (2)
.withCallback( (3)
ExpressionCreatedEventType.ON_RETURN_ITEM,
AliasedExpression.class,
ensureAlias
)
.build();
var userStatement = CypherParser.parse(userProvidedCypher, options); (4)
1 | This is a Function that receives an expressions and returns a new one. It
checks if the provided expressions obeys to some criteria: Here being something that is aliased or not |
2 | We start building new options |
3 | The callback from step one is passed as callback to the event ON_RETURN_ITEM and will be called for every item |
4 | The final option instance will be applied to the parser. The statement will render to the same result as the first example. |
5.3.3. Preventing certain things
Callbacks can of course be used to prevent things. Any exception thrown will halt the parsing. Listing 21 shows how:
var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) REMOVE this.something RETURN o";
UnaryOperator<Expression> preventPropertyDeletion = r -> {
throw new RuntimeException("Not allowed to remove properties!"); (1)
};
var options = Options.newOptions()
.withCallback( (2)
ExpressionCreatedEventType.ON_REMOVE_PROPERTY,
Expression.class,
preventPropertyDeletion
)
.build();
assertThatExceptionOfType(RuntimeException.class)
.isThrownBy(() -> CypherParser.parse(userProvidedCypher, options)); (3)
1 | Create a callback that just throws an unchecked exception |
2 | Configure it for the event that should be prevented |
3 | Parsing will not be possible |
5.3.4. Shape the return clause the way you want
The parser provides ReturnDefinition
as value object. It contains information to be passed to the Clauses
factory to shape
a RETURN
clause the way you need:
var userProvidedCypher = "MATCH (this)-[:LINK]-(o:Other) RETURN distinct this, o LIMIT 23";
Function<ReturnDefinition, Return> returnClauseFactory = d -> { (1)
var finalExpressionsReturned = d.getExpressions().stream()
.filter(e -> e instanceof SymbolicName && "o".equals(((SymbolicName) e).getValue()))
.map(e -> e.as("result"))
.collect(Collectors.<Expression>toList());
return Clauses.returning(
false,
finalExpressionsReturned,
List.of(Cypher.name("o").property("x").descending()),
d.getOptionalSkip(), d.getOptionalLimit()
);
};
var options = Options.newOptions()
.withReturnClauseFactory(returnClauseFactory) (2)
.build();
var userStatement = CypherParser.parse(userProvidedCypher, options);
var cypher = userStatement.getCypher();
assertThat(cypher) (3)
.isEqualTo("MATCH (this)-[:`LINK`]-(o:`Other`) RETURN o AS result ORDER BY o.x DESC LIMIT 23");
1 | Create a factory method that takes in a definition and uses its information to build the RETURN .
Or examples filters the attributes being returned and enforces an alias.
It also adds some arbitrary sorting and keeps sort and limit values from the original |
2 | It than is parsed to the options |
3 | The statement has the new RETURN clause. |
5.3.5. Enforcing labels
The parser can enforce labels to be present or absent with filters. This can be individually achieved when parsing node patterns,
setting or removing labels with a BiFunction
like the following:
final BiFunction<LabelParsedEventType, Collection<String>, Collection<String>> makeSureALabelIsPresent = (e, c) -> {
var finalLabels = new LinkedHashSet<>(c);
switch (e) { (1)
case ON_NODE_PATTERN:
finalLabels.add("ForcedLabel");
return finalLabels;
case ON_SET:
finalLabels.add("Modified");
return finalLabels;
case ON_REMOVE:
finalLabels.remove("ForcedLabel");
return finalLabels;
default:
return c;
}
};
1 | Decide on the event type what is supposed to happen |
Putting this function in action involves the Options
class again:
var options =
Options.newOptions().withLabelFilter(makeSureALabelIsPresent).build();
var statement = CypherParser
.parseStatement("MATCH (n:Movie) RETURN n", options)
.getCypher();
assertThat(statement).isEqualTo("MATCH (n:`Movie`:`ForcedLabel`) RETURN n");
This can safely be used to match only nodes spotting such a label for example.
var options =
Options.newOptions().withLabelFilter(makeSureALabelIsPresent).build();
var statement = CypherParser
.parseStatement("MATCH (n:Movie) SET n:`Comedy` RETURN n", options)
.getCypher();
assertThat(statement).isEqualTo("MATCH (n:`Movie`:`ForcedLabel`) SET n:`Comedy`:`Modified` RETURN n");
Of course, we can prevent a label to be removed:
var options = Options.newOptions().withLabelFilter(makeSureALabelIsPresent).build();
var statement = CypherParser
.parseStatement("MATCH (n:Movie) REMOVE n:`Comedy`:`ForcedLabel` RETURN n", options)
.getCypher();
assertThat(statement).isEqualTo("MATCH (n:`Movie`:`ForcedLabel`) REMOVE n:`Comedy` RETURN n");
Changing relationship types via a filter is possible as well, but as relationships might only have one type, the number of usecases is smaller.
5.3.6. Combining the parser with SDN’s CypherdslConditionExecutor
Spring Data Neo4j 6 provides CypherdslConditionExecutor
. This is a fragment that adds the capability to execute
statements with added conditions to a Neo4jRepository
.
Given the following repository:
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor;
import org.springframework.data.neo4j.repository.support.CypherdslStatementExecutor;
public interface PeopleRepository extends Neo4jRepository<Person, Long>,
CypherdslConditionExecutor<Person>, (1)
CypherdslStatementExecutor<Person> { (2)
}
1 | Allows to just add conditions to our generated queries |
2 | Provides an alternative to using @Query with strings |
One possible use case is presented in this service:
Iterable<Person> findPeopleBornAfterThe70tiesAnd(String additionalConditions) {
return peopleRepository.findAll(
person.BORN.gte(Cypher.literalOf(1980))
.and(CypherParser.parseExpression(additionalConditions).asCondition()) (1)
);
}
1 | The condition that only people born later than 1980 is hard coded in the service.
An arbitrary String is than parsed into a condition and attached via AND .
Thus, only valid cypher can go in there and with filters and callbacks, preconditions of that Cypher can be asserted. |
The downside to the above solution is that the query fragment passed to the service and eventually the repository must
know the root node (which is n
in case of SDN 6) and the caller code might look like this:
var exchange = restTemplate.exchange(
"/api/people/v1/findPeopleBornAfterThe70ties?conditions={conditions}",
HttpMethod.GET,
null, new ParameterizedTypeReference<List<Person>>() { },
"person.name contains \"Ricci\" OR person.name ends with 'Hirsch'"
);
Notice n.name
etc.
We could change the service method slightly and apply a callback like this:
Iterable<Person> findPeopleBornAfterThe70tiesAndV2(String additionalConditions) {
Function<Expression, Expression> enforceReference =
e -> personRootName.property(((SymbolicName) e).getValue()); (1)
var parserOptions = Options.newOptions()
.withCallback(
ExpressionCreatedEventType.ON_NEW_VARIABLE,
Expression.class,
enforceReference
) (2)
.build();
return peopleRepository.findAll(
person.BORN.gte(Cypher.literalOf(1980)).and(
CypherParser.parseExpression(
additionalConditions,
parserOptions (3)
).asCondition()
)
);
}
1 | Create a function that takes the value of a variable created in the fragment and use it to look up a property on the SDN root node. |
2 | Create an instance of parsing options. It’s probably a good idea todo this once and store them away in an instance variable. Options are thread safe. |
3 | Apply them when calling the corresponding parse method |
Now it’s enough to pass "name contains \"Ricci\" OR name ends with 'Hirsch'"
into the exchange presented above and things
will work out of the box.
Further validation and sanitiy checks are of course up to you.
6. Integration with the Neo4j-Java-Driver
6.1. Introduction
The Cypher-DSL is - apart from API Guardian - dependency free. It has however some optional dependency you can add to use more functionality. One of those dependencies is the Neo4j-Java-Driver. The Neo4j-Java-Driver - or sometimes Bolt for Java or Bolt-Driver - implements Neo4j’s Bolt protocol and provides a connection to a single Neo4j instance or a cluster.
While the Cypher-DSL creates statements that eventually will be rendered as a String
, it has some knowledge about the
statements generated:
-
Will the statements return something?
-
Or will the statements just run updates?
-
Will the statements be profiled?
-
Do the statements carry parameter definitions and values for parameters?
We can use that knowledge to provide a thin shim for using the Cypher-DSL with the API the Neo4j-Java-Driver provides without reinventing the wheel.
In most cases the Cypher-DSL will generate one of two types of statements:
-
org.neo4j.cypherdsl.core.Statement
: A statement without a result. It can be executed, but it cannot be fetched. You will be able to check for the number of affected database entities via the driver’sResultSummary
. -
org.neo4j.cypherdsl.core.ResultStatement
: A statement known to have a result. It can be executed or fetched.
Both interfaces can be turned into ExecutableStatements, providing the appropriate methods, which will be discussed below.
This API is not required to use the driver nor is the driver required for using the Cypher-DSL.
The main API provided with the Cypher-DSL is Statement#getCypher() for retrieving a Cypher-String and optional call
to Statement#getParameters() to retrieve parameters stored with the statement.
Those can be used in many ways and forms with the Neo4j-Java-Driver or mapping frameworks like Neo4j-OGM or Spring Data Neo4j 6+. |
6.2. Add additional dependencies
<dependency>
<groupId>org.neo4j.driver</groupId>
<artifactId>neo4j-java-driver</artifactId>
<artifactId>5.25.0</artifactId>
</dependency>
Any 4.x version of the Driver will work, we currently test against 5.25.0. In case you want to use our integration with reactive sessions, you will have to add Project Reactor. To the outside world we expose - much like the driver - the vendor agnostic Reactive Streams spec.
The coordinates of Project reactor are io.projectreactor:reactor-core
. Those can be added in the appropriate form to a
Maven or Gradle project.
We test with 2023.0.10 of Reactor and use their BOM module imported into dependency management instead of a fixed
version for the core module itself:
<dependencyManagement>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-bom</artifactId>
<version>2023.0.10</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencyManagement>
6.3. Imperative (blocking) API
You create an instance of the Neo4j driver. This instance should be a long living instance in your application:
driver = GraphDatabase.driver(neo4j.getBoltUrl(), AuthTokens.basic("neo4j", neo4j.getAdminPassword()));
Both org.neo4j.driver.Session
and org.neo4j.driver.Transaction
implement a QueryRunner
(the same applies for the
reactive variants).
It is your responsibility to pick the right kind of interaction with the database (auto commit, transactions or transactional functions).
If you pass a Session
to the Cypher-DSL, auto commit transactions will be used. If you pass in a transaction object,
unmanaged transactions will be used and you must commit or rollback as needed and take care for retries.
A safe bet is to pass one of the Cypher-DSL methods to the driver as a READ or WRITE transaction.
The Cypher-DSL won’t close, commit or rollback anything you pass to it. You open resources, you close them. In all cases where the Cypher-DSL opens or provides a resource, it will take care closing it correctly. |
Our goal when writing this small integration with the official driver was to apply best practices when working with the driver without adding too much cognitive overhead or introducing new types.
6.3.1. Executing statements
We first have a look at statements that don’t return data, but may execute updates or call stored procedures. We will use the following listing as example:
var m = Cypher.node("Test").named("m");
var statement = ExecutableStatement.of(Cypher.create(m)
.set(m.property("name").to(Cypher.anonParameter("statementWithoutResult")))
.build());
Inside transactional functions
The driver has the concept of transactional functions. This basically describes a transaction manager understanding the semantics of a Neo4j cluster, possible errors that can occur, routing and such. The transaction manager is able to retry statements in certain error cases.
Transactional functions are passed as TransactionalWork<T>
to a session.
The executeWith
API on Statement
fits this interface and you can pass it as a method reference.
It will always return a result summary:
try (var session = driver.session()) { (1)
var summary = session.executeWrite(statement::executeWith); (2)
assertThat(summary.counters().nodesCreated()).isEqualTo(1); (3)
}
1 | Open a session |
2 | Pass the executeWith method from the Cypher-DSL statement to the driver |
3 | Do something with the result summary |
Read more about transactional functions in the official manual: Imperative sessions and transaction functions. |
With an unmanaged transaction
A transaction you retrieve from a session is called unmanaged transaction. It is your responsibility to commit or rollback and close it.
try (var session = driver.session();
var tx = session.beginTransaction() (1)
) {
var summary = statement.executeWith(tx); (2)
tx.commit(); (3)
assertThat(summary.counters().nodesCreated()).isEqualTo(1);
}
1 | Get a transaction from the session |
2 | Pass it to the statement, execute and get the result summary |
3 | Do more things in the transaction, than commit or rollback it |
The advantage here is that you have more control and are not limited to idempotent operations for the transactional function. The disadvantage has been hidden away in that example: You have to take care of exceptions that might happen due to cluster errors yourself.
If you want to use an auto-commit transaction (for example for a PERIODIC COMMIT
or an APOC call), just pass a Session
to executeWith
.
6.3.2. Fetching data
The following examples will use this statement, returning the title of movies in the Movie-Graph:
var m = Cypher.node("Movie").named("m");
var statement = ExecutableStatement.of(Cypher.match(m)
.returning(m.property("title").as("title")).build());
We provide two forms of accessing data via the imperative API: Pulling everything into a list or streaming records. The reactive API is fully non-blocking and uses a publishing API.
Inside transactional functions
try (var session = driver.session()) { (1)
var moviesTitles = session.executeRead(statement::fetchWith) (2)
.stream() (3)
.map(r -> r.get("title").asString())
.collect(Collectors.toList());
assertMovieTitleList(moviesTitles);
}
1 | Open a session |
2 | Pass the fetchWith method from the Cypher-DSL statement to the driver |
3 | The readTransaction (or writeTransaction ) returns whatever the transactional work returns
Our transactional work fetchWith returns a java.util.List of records
Here we turn it into a Java stream (not connected to Neo4j) and map and collect it |
fetchWith
does support a second parameter, a mapping function Function<Record, T> mappingFunction
. We can use this
with a transactional function as well and do the mapping inside the transaction.
For the example, this form here is easier to read, but the other one may have benefits in regards of memory allocation.
You also can stream the result. In contrast to the plain Driver API, we don’t hand out the stream itself but expect a closure dealing with the stream. We do this to make it impossible to use the stream - which is still connected to the database - outside the transaction. With managed transactions, it looks like this:
try (var session = driver.session()) { (1)
var summary = session.executeRead(tx -> (2)
statement.streamWith(tx, s -> { (3)
var moviesTitles = s.map(r -> r.get("title").asString())
.collect(Collectors.toList()); (4)
assertMovieTitleList(moviesTitles);
})
);
assertThat(summary.query().text()).isEqualTo(statement.getCypher());
}
1 | Open a session |
2 | Open a managed transaction, but don’t pass a method reference this time |
3 | Instead, call streamWith , pass the transaction and a consumer for the stream s . |
4 | Inside this example, we fully materialize the stream and map records as they pass by As the streaming method will always return the result summary, this will also be the type of the transaction function |
With an unmanaged transaction
Now the same examples with unmanaged transactions. First, listening them. Here we pass a mapping function directly to fetchWith
.
The pattern of opening a session and transaction is the same as before.
try (var session = driver.session();
var tx = session.beginTransaction()) {
var moviesTitles = statement.fetchWith(
tx, (1)
r -> r.get("title").asString() (2)
);
tx.commit();
assertMovieTitleList(moviesTitles);
}
1 | The unmanaged transaction passed to the statement, |
2 | and the mapping function applied. moviesTitle is a List containing String elements in the end. |
The streaming example looks pretty similar to the one applied with the transactional function.
As we pass the Session
object itself, we are using an auto-commit transaction.
try (var session = driver.session()) {
var summary = statement.streamWith(session, stream -> {
var moviesTitles = stream
.map(r -> r.get("title").asString())
.collect(Collectors.toList());
assertMovieTitleList(moviesTitles);
});
assertThat(summary.query().text()).isEqualTo(statement.getCypher());
}
Again, please note that the stream is only available in the closure. It is not meant to live outside the transactional scope.
6.4. Reactive API
For the reactive API the variants inside a transactional function are focused here as they are probably the most complex ones to get right: You have to deal with to resources: The session itself, and the inner publisher returned from the transactional function.
To create a reactive, executable statement from either a Statement
or ResultStatement
use the factory methods from ReactiveExecutableStatement
:
var m = Cypher.node("Test").named("m");
var statement = ReactiveExecutableStatement.of(Cypher.create(m)
.set(m.property("name").to(Cypher.anonParameter("statementsWithoutResultReactive")))
.build());
6.4.1. Executing statements
The most important part in the following example is not necessarily our API, but the use of Reactors fromDirect
operator when converting
a publisher of a single element into a Mono
: That operator trusts the user that the publisher will return zero or one element
and doesn’t cancel after the first item being emitted and thus rolling back the transaction:
Mono.usingWhen(
Mono.fromSupplier(() -> driver.session(ReactiveSession.class)), (1)
s -> Mono.fromDirect(s.executeWrite(statement::executeWith)), (2)
ReactiveSession::close (3)
).as(StepVerifier::create)
.expectNextMatches(r -> r.counters().nodesCreated() == 1) (4)
.verifyComplete();
1 | The session should be opened non-blocking and as late as possible |
2 | This is the actual call from the Cypher-DSL into the driver |
3 | Closed what you opened |
4 | The reactive executeWith -variant returns the summary as well |
6.4.2. Fetching results
With a reactive, transactional function
This example uses our original statement and arbitrary skips 2 elements and then takes only 30 elements.
As the transaction is managed by the driver, canceling the publisher via take()
won’t rollback the transaction.
Flux.usingWhen(
Mono.fromSupplier(() -> driver.session(ReactiveSession.class)),
s -> s.executeRead(statement::fetchWith),
ReactiveSession::close
)
.skip(2)
.take(30)
.as(StepVerifier::create)
.expectNextCount(30)
.verifyComplete();
Inside an auto-commit transaction
One use case might be the bulk loading of data.
By using an auto-commit transaction we effectively state that we neither manage a transaction ourselves nor we want the driver
to manage one for us.
In this case we use Neo4j’s LOAD CSV
clause that basically describes a Cypher script that runs in its own transaction.
The server might or might not start streaming results after the configured periodic commit rate has been hit:
PERIODIC COMMIT LOAD CSV
from a reactive auto-commit transaction:var row = Cypher.name("row");
var a = Cypher.node("Author").withProperties("name", Cypher.trim(Cypher.name("author"))).named("a");
var m = Cypher.node("Book").withProperties("name", row.property("Title")).named("b");
var statement = ReactiveExecutableStatement.of(Cypher.usingPeriodicCommit(10)
.loadCSV(URI.create("file:///books.csv"), true).as(row).withFieldTerminator(";")
.create(m)
.with(m.getRequiredSymbolicName(), row)
.unwind(Cypher.split(row.property("Authors"), "&")).as("author")
.merge(a)
.create(a.relationshipTo(m, "WROTE").named("r"))
.returningDistinct(m.property("name").as("name"))
.build());
Flux.using(() -> driver.session(ReactiveSession.class), statement::fetchWith, ReactiveSession::close)
.as(StepVerifier::create)
.expectNextCount(50)
.verifyComplete();
7. Building a static meta model
7.1. Concepts
7.1.1. A static meta model is optional
First let’s stress this: a static meta model is optional for the Cypher-DSL. It is completely OK to use the Cypher-DSL as shown in the examples in the "How to use it" part: all labels, types and properties can be named as you go. The Cypher-DSL will still give you type-safety in regard to the Cypher’s syntax.
This fits nicely with Neo4j’s capabilities: Neo4j is a schemaless database. Nodes, their relationships between each other, their labels and properties can be changed as you go but all of this information can still be queried.
In a schemaless database or a database with dynamic scheme the scheme is often defined by the application. This definition takes many forms: It can be through an object mapper like Neo4j-OGM or Spring Data Neo4j 6+, or maybe in form of Graph-QL schemes.
Another source may be the information we can retrieve from the database itself via db.schema.nodeTypeProperties
and
db.schema.relTypeProperties
.
7.1.2. Building blocks
The Cypher DSL offers the following building blocks as part of the public API:
-
Two pattern elements:
-
Nodes via
Node
(and its default implementationNodeBase
) -
Relationships via
Relationship
(and its default implementationRelationshipBase
)
-
-
Property
, which is a holder for properties
When you use Cypher-DSL like this:
var m = Cypher.node("Movie").named("m");
var statement = Cypher.match(m).returning(m).build();
m
will be a Node
instance having the label Movie
and an alias of m
. m
can be used everywhere where a pattern element
can be used according to the openCypher spec.
You don’t have to care about its type. That’s why we vouched for the JDK11+ local type inference here and omitted the
declaration of the type Node
: it just reads better.
7.1.3. A very simple, static model
Both NodeBase
and RelationshipBase
are meant to be extended to put your static model into something that is usable
with the Cypher-DSL.
Nodes
Start by extending NodeBase
like this:
import java.util.List;
import org.neo4j.cypherdsl.core.MapExpression;
import org.neo4j.cypherdsl.core.NodeBase;
import org.neo4j.cypherdsl.core.NodeLabel;
import org.neo4j.cypherdsl.core.Properties;
import org.neo4j.cypherdsl.core.SymbolicName;
public final class Movie extends NodeBase<Movie> { (1)
public static final Movie MOVIE = new Movie(); (2)
public Movie() {
super("Movie"); (3)
}
private Movie(SymbolicName symbolicName, List<NodeLabel> labels, Properties properties) { (4)
super(symbolicName, labels, properties);
}
@Override
public Movie named(SymbolicName newSymbolicName) { (5)
return new Movie(newSymbolicName, getLabels(), getProperties());
}
@Override
public Movie withProperties(MapExpression newProperties) { (6)
return new Movie(getSymbolicName().orElse(null), getLabels(), Properties.create(newProperties));
}
}
1 | Extend from NodeBase and specify your class as a "self" type-argument |
2 | Optional: Create one static instance of your model |
3 | This is where you specify one or more label |
4 | This constructor is optional, it is used in the next two steps |
5 | named must be overridden and must return new copies of the node, with the changed symbolic name.
It must be overridden to guarantee type integrity. |
6 | Same as above |
With that in place, you can already use it like this:
var cypher = Cypher.match(Movie.MOVIE)
.returning(Movie.MOVIE)
.build().getCypher();
and it would generate a Cypher string like this: "MATCH (jwKyXzwS000:`Movie
) RETURN jwKyXzwS000"`, with generated variable names.
If you don’t like them, you can just rename one instance of the movie-model like this:
var movie = Movie.MOVIE.named("m");
var cypher = Cypher.match(movie)
.returning(movie)
.build().getCypher();
Of course, properties belong into a model as well. You add them like this:
import org.neo4j.cypherdsl.core.Property;
public final class Movie extends NodeBase<Movie> { (1)
public final Property TITLE = this.property("title"); (2)
}
1 | Same class before, extending from NodeBase . |
2 | Use this and the property method to create a new Property instance, stored on the given instance |
A possible usage scenario looks like this:
var movie = Movie.MOVIE.named("m");
var cypher = Cypher.match(movie)
.where(movie.TITLE.isEqualTo(Cypher.literalOf("The Matrix"))) (1)
.returning(movie)
.build().getCypher();
Assertions.assertThat(cypher)
.isEqualTo("MATCH (m:`Movie`) WHERE m.title = 'The Matrix' RETURN m");
1 | Make sure to use the renamed instance everywhere. Here: For accessing the property. Alternatively, don’t rename. |
Relationships
Relationships are a bit more complicated. Relationships of the same type can be used between nodes with different labels. We have these scenarios:
-
(s:LabelA) - (r:SomeType) → (e:LabelB)
: -
(s:LabelA) - (r:SomeType) → (e)
-
(s) - (r:SomeType) → (e)
We either have a type that is used only between the same set of labels, or a type is used always with one fixed label or a type is used between arbitrary labels.
To accommodate for that, the default relationship implementation, RelationshipBase
, spots three type parameters:
public class RelationshipBase<S extends NodeBase<?>, E extends NodeBase<?>, SELF extends RelationshipBase<S, E, SELF>> {
}
S
is the type of a start node, E
of an end node and SELF
is the concrete implementation itself.
The public API of RelationshipBase
enforces a direction from start to end (LTR
, left to right).
We just have a look at the first case to make the concepts clear. We model the ACTED_IN
relationship of the movie graph.
It exists between people and movies (going from person to movie) and has an attribute roles:
import org.neo4j.cypherdsl.core.MapExpression;
import org.neo4j.cypherdsl.core.Node;
import org.neo4j.cypherdsl.core.Properties;
import org.neo4j.cypherdsl.core.Property;
import org.neo4j.cypherdsl.core.RelationshipBase;
import org.neo4j.cypherdsl.core.SymbolicName;
public final class ActedIn extends RelationshipBase<Person, Movie, ActedIn> { (1)
public static final String $TYPE = "ACTED_IN";
public final Property ROLE = this.property("role"); (2)
protected ActedIn(Person start, Movie end) {
super(start, $TYPE, end); (3)
}
private ActedIn(SymbolicName symbolicName, Node start, Properties properties, Node end) { (4)
super(symbolicName, start, $TYPE, properties, end);
}
@Override
public ActedIn named(SymbolicName newSymbolicName) { (5)
return new ActedIn(newSymbolicName, getLeft(), getDetails().getProperties(), getRight());
}
@Override
public ActedIn withProperties(MapExpression newProperties) { (6)
return new ActedIn(getSymbolicName().orElse(null), getLeft(), Properties.create(newProperties), getRight());
}
}
1 | The base class, with 3 concrete types. A Person , the Movie from [static-movie] and the type itself. |
2 | Same idea with as with nodes: Store all properties as final attributes. |
3 | There is no default constructor for a relationship: Start and end node must be specified. The type is fixed. |
4 | Copy constructor for the following two methods |
5 | Required to rename this relationship |
6 | Required for querying properties |
In contrast to the movie, we don’t need a static attribute allowing access to the relationship.
This is stored at the owner. In this case, the Person
:
public final class Person extends NodeBase<Person> {
public static final Person PERSON = new Person();
public final Directed<Movie> DIRECTED = new Directed<>(this, Movie.MOVIE);
public final ActedIn ACTED_IN = new ActedIn(this, Movie.MOVIE); (1)
public final Property NAME = this.property("name");
public final Property FIRST_NAME = this.property("firstName");
public final Property BORN = this.property("born");
}
1 | We create the relationship properties with this concrete instance pointing to another, concrete instance. |
7.2. Possible Usage
Please assume we did model the "Movie graph" (:play movies
in Neo4j-Browser) with the following scheme:
and these classes, which have been generated with the building blocks described earlier (the required functions have been omitted for brevity):
final class Movie extends NodeBase<Movie> {
public static final Movie MOVIE = new Movie();
public final Property TAGLINE = this.property("tagline");
public final Property TITLE = this.property("title");
public final Property RELEASED = this.property("released");
public Movie() {
super("Movie");
}
}
final class Person extends NodeBase<Person> {
public static final Person PERSON = new Person();
public final Property NAME = this.property("name");
public final Property FIRST_NAME = this.property("firstName");
public final Directed<Movie> DIRECTED = new Directed<>(this, Movie.MOVIE);
public final ActedIn ACTED_IN = new ActedIn(this, Movie.MOVIE);
public final Property BORN = this.property("born");
public final Property DOB = this.property("dob");
public Person() {
super("Person");
}
}
final class ActedIn extends RelationshipBase<Person, Movie, ActedIn> {
public static final String $TYPE = "ACTED_IN";
public final Property ROLE = this.property("role");
protected ActedIn(Person start, Movie end) {
super(start, $TYPE, end);
}
}
final class Directed<E extends NodeBase<?>> extends RelationshipBase<Person, E, Directed<E>> {
public static final String $TYPE = "DIRECTED";
protected Directed(Person start, E end) {
super(start, $TYPE, end);
}
}
7.2.1. Work with properties
Properties can be used like normal objects:
var cypher = Cypher.match(Person.PERSON)
.returning(Person.PERSON.NAME, Person.PERSON.BORN)
.build().getCypher();
Assertions.assertThat(cypher)
.matches("MATCH \\(\\w+:`Person`\\) RETURN \\w+\\.name, \\w+\\.born");
Of course, new properties can be derived:
var cypher = Cypher.match(Person.PERSON)
.returning(Person.PERSON.NAME.concat(Cypher.literalOf(" whatever")))
.build().getCypher();
Assertions.assertThat(cypher)
.matches("MATCH \\(\\w+:`Person`\\) RETURN \\(\\w+\\.name \\+ ' whatever'\\)");
7.2.2. Query nodes or relationships by properties
Use withProperties
(and named
you like) to model your queries as needed.
Applicable to properties of nodes such as the title:
var movie = Movie.MOVIE.withProperties(Movie.MOVIE.TITLE, Cypher.literalOf("The Matrix")).named("m1");
var cypher = Cypher.match(movie)
.returning(movie)
.build().getCypher();
Assertions.assertThat(cypher)
.isEqualTo("MATCH (m1:`Movie` {title: 'The Matrix'}) RETURN m1");
and relationships:
var actedIn = Person.PERSON.ACTED_IN.withProperties(Person.PERSON.ACTED_IN.ROLE, Cypher.literalOf("Neo"));
var cypher = Cypher.match(actedIn)
.returning(Movie.MOVIE)
.build().getCypher();
Assertions.assertThat(cypher)
.matches("MATCH \\(\\w+:`Person`\\)-\\[\\w+:`ACTED_IN` \\{role: 'Neo'}]->\\(\\w+:`Movie`\\) RETURN \\w+");
Note that the query will look like this, as we didn’t rename the objects and they used generated names:
`MATCH (dZVpwHhe000:`Person`)-[JcVKsSrn001:`ACTED_IN` {role: 'Neo'}]->(cDWeUJSI002:`Movie`) RETURN cDWeUJSI002`` as we didn't specify aliases)
7.2.3. Work with relationships
Relationships can be worked with like with properties:
var cypher = Cypher.match(Person.PERSON.DIRECTED)
.match(Person.PERSON.ACTED_IN)
.returning(Person.PERSON.DIRECTED, Person.PERSON.ACTED_IN)
.build().getCypher();
They are quite flexible together with the inverse
method. The following example also shows how to include non-static parts:
var otherPerson = Person.PERSON.named("o");
var cypher = Cypher.match(
Person.PERSON.DIRECTED.inverse()
.relationshipTo(otherPerson, "FOLLOWS") (1)
)
.where(otherPerson.NAME.isEqualTo(Cypher.literalOf("Someone")))
.returning(Person.PERSON)
.build().getCypher();
Assertions.assertThat(cypher)
.matches(
"MATCH \\(\\w+:`Movie`\\)<-\\[:`DIRECTED`]-\\(\\w+:`Person`\\)-\\[:`FOLLOWS`]->\\(o:`Person`\\) WHERE o\\.name = 'Someone' RETURN \\w+");
1 | Using a non-static fragment |
7.3. The Spring Data Neo4j 6 annotation processor
We provide a Java annotation processor for Spring Data Neo4j under the following coordinates:
org.neo4j:neo4j-cypher-dsl-codegen-sdn6:2024.2.0
The annotation processor understands classes annotated with @Node
and @RelationshipProperties
.
Inside those classes @Relationship
and @Property
are read.
The processor generates a static meta model for each annotated class found in the same package with an underscore (_
) added to the name.
The processor needs Spring Data Neo4j 6 and the Cypher-DSL in version 2021.1.0
or later on it’s classpath.
We recommend using it explicitly on the separate annotation processor classpath (via --processor-path
to javac
).
Please make sure to use @Relationship when you use the annotation processor. While we do our best to detect possible,
implicit associations during annotation processing, we can’t load classes that are being processed that very moment
to check if the Spring infrastructure would provide a custom converter for them and make them a simple property.
We won’t generate fields when in doubt but relationships if we find the corresponding class.
|
7.3.1. Configure your build
Maven
As a Maven user, please configure the build as follows:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher-dsl-codegen-sdn6</artifactId>
<version>{neo4j-cypher-dsl.version}</version>
</annotationProcessorPath>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
Gradle
In a Gradle project, please add the following:
dependencies {
annotationProcessor 'org.neo4j:neo4j-cypher-dsl-codegen-sdn6:2024.2.0'
}
As dependency on the classpath
We recommend the annotation processor path for two reasons: The processor needs SDN as dependency. While SDN is already on the classpath, it might not fit the one we build the annotation processor against exactly. While the processor is lenient in that regard, your dependency setup might not. Furthermore: Why should you have the annotation processor as a dependency in your final artifact? This would be unnecessary. |
If you insist on having the SDN 6 annotation processor on the standard class path, please include with your Spring Data Neo4j 6 application like as follows to avoid dependency conflicts:
<dependency>
<groupId>org.neo4j</groupId>
<artifactId>neo4j-cypher-dsl-codegen-sdn6</artifactId>
<version>{neo4j-cypher-dsl.version}</version>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
</exclusion>
<exclusion>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-neo4j</artifactId>
</exclusion>
</exclusions>
</dependency>
7.3.2. Usage
The processor supports the following arguments:
Name | Meaning |
---|---|
org.neo4j.cypherdsl.codegen.prefix, |
An optional prefix for the generated classes |
org.neo4j.cypherdsl.codegen.suffix |
An optional suffix for the generated classes |
org.neo4j.cypherdsl.codegen.indent_style |
The indent style (Use |
org.neo4j.cypherdsl.codegen.indent_size |
The number of whitespaces for the indent style |
org.neo4j.cypherdsl.codegen.timestamp |
An optional timestamp in ISO_OFFSET_DATE_TIME format for the generated classes. Defaults to the time of generation. |
org.neo4j.cypherdsl.codegen.add_at_generated |
A flag whether |
org.neo4j.cypherdsl.codegen.sdn.custom_converter_classes |
A comma separated list of custom Spring converter classes (Can be |
The generated classes can be used in a variety of places:
import java.util.Collection;
import java.util.List;
import org.neo4j.cypherdsl.core.Cypher;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
@Service
final class MovieService {
private final MovieRepository movieRepository;
MovieService(MovieRepository movieRepository) {
this.movieRepository = movieRepository;
}
List<Movie> findAll() {
return movieRepository
.findAll(Sort.by(Movie_.MOVIE.TITLE.getName()).ascending()); (1)
}
}
1 | Pass the name of the property TITLE to Spring Data’s sort facility |
Spring Data Neo4j 6.1 has two additional query fragments, that makes working with the Cypher-DSL very efficient:
import org.springframework.data.neo4j.repository.Neo4jRepository;
import org.springframework.data.neo4j.repository.support.CypherdslConditionExecutor;
import org.springframework.data.neo4j.repository.support.CypherdslStatementExecutor;
public interface PeopleRepository extends Neo4jRepository<Person, Long>,
CypherdslConditionExecutor<Person>, (1)
CypherdslStatementExecutor<Person> { (2)
}
1 | Allows to just add conditions to our generated queries |
2 | Provides an alternative to using @Query with strings |
Both interfaces are independent of each other, they can be used together like in this example or separately (just picking the one you need). You can read more about them in the Spring Data Neo4j 6.1 documentation, chapter Spring Data Neo4j Extensions.
The repository above can be used like this:
import java.util.Optional;
import java.util.function.Function;
import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.Expression;
import org.springframework.data.neo4j.core.mapping.Constants;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.stereotype.Service;
@Service
final class PeopleService {
private final Person_ person;
private final SymbolicName personRootName;
private final PeopleRepository peopleRepository;
PeopleService(PeopleRepository peopleRepository, Neo4jMappingContext mappingContext) {
this.peopleRepository = peopleRepository;
this.personRootName = Constants.NAME_OF_TYPED_ROOT_NODE.apply(
mappingContext.getRequiredPersistentEntity(Person.class));
this.person = Person_.PERSON.named(personRootName);
}
Iterable<Person> findPeopleBornInThe70tiesOr(Optional<String> optionalName) {
return peopleRepository.findAll(
person.BORN.gte(Cypher.literalOf(1970)).and(person.BORN.lt(Cypher.literalOf(1980))) (1)
.or(optionalName
.map(name -> person.NAME.isEqualTo(Cypher.anonParameter(name))) (2)
.orElseGet(Cypher::noCondition)) (3)
);
}
Optional<PersonDetails> findDetails(String name) {
var d = Movie_.MOVIE.named("d");
var a = Movie_.MOVIE.named("a");
var m = Movie_.MOVIE.named("movies");
var r = Cypher.anyNode("relatedPerson");
var statement = Cypher.match(Person_.PERSON.withProperties("name", Cypher.anonParameter(name)))
.optionalMatch(d.DIRECTORS)
.optionalMatch(a.ACTORS)
.optionalMatch(Person_.PERSON.relationshipTo(m).relationshipFrom(r, ActedIn_.$TYPE))
.returningDistinct(
Person_.PERSON.getRequiredSymbolicName(),
Cypher.collectDistinct(d).as("directed"),
Cypher.collectDistinct(a).as("actedIn"),
Cypher.collectDistinct(r).as("related")).build();
return peopleRepository.findOne(statement, PersonDetails.class); (4)
}
}
1 | Using literals |
2 | Using an optional parameter |
3 | or when it’s not filled, an empty condition |
4 | Project a person onto PersonDetails , using a complex query. |
Here is another complex example. The MovieRepository
is also a CypherdslStatementExecutor
:
Collection<Movie> findAllRelatedTo(Person person) {
var p = Person_.PERSON.named("p");
var a = Movie_.MOVIE.named("a");
var d = Movie_.MOVIE.named("d");
var m = Cypher.name("m");
var statement = Cypher.match(p)
.where(p.NAME.isEqualTo(Cypher.anonParameter(person.getName()))) (1)
.with(p)
.optionalMatch(new ActedIn_(p, a))
.optionalMatch(new Directed_(p, d))
.with(Cypher.collect(a).add(Cypher.collect(d))
.as("movies"))
.unwind("movies").as(m)
.returningDistinct(m)
.orderBy(Movie_.MOVIE.named(m).TITLE).ascending()
.build();
return this.movieRepository.findAll(statement);
}
1 | Here we use an anonymous parameter to store the name value |
The full example is in neo4j-cypher-dsl-examples/neo4j-cypher-dsl-examples-sdn6
.
An example of using a mixture of anonymous and named parameters is shown in the following listing:
Optional<Person> createNewPerson(NewPersonCmd newPersonCmd) {
var p = Person_.PERSON.withProperties(
Person_.PERSON.NAME, Cypher.anonParameter(newPersonCmd.getName()) (1)
).named("p");
var statement = Cypher.merge(p)
.onCreate().set(
p.BORN, Cypher.parameter("arbitraryName")
.withValue(newPersonCmd.getDob().getYear()), (2)
p.DOB, Cypher.anonParameter(newPersonCmd.getDob()) (3)
).returning(p).build();
return peopleRepository.findOne(statement);
}
1 | An anonymous parameter with a simple String -value |
2 | A named parameter with an Integer value extracted from a complex datatype |
3 | An anonymous parameter again, but one that holds a complex datatype. Such datatype can be anything that is understood by the Neo4j-Java-Driver, such as in this case, a temporal but also maps and lists. It will always be passed as a value, never as a literal. |
The statement above will be rendered like this:
[http-nio-auto-1-exec-1] 2022-06-21 11:40:57,732 DEBUG org.springframework.data.neo4j.cypher: 313 - Executing:
MERGE (p:`Person` {name: $pcdsl01}) ON CREATE SET p.born = $arbitraryName, p.dob = $pcdsl02 RETURN p
[http-nio-auto-1-exec-1] 2022-06-21 11:40:57,735 TRACE org.springframework.data.neo4j.cypher: 334 - with parameters:
:param arbitraryName => 1990
:param pcdsl01 => 'Liv Lisa Fries'
:param pcdsl02 => 1990-10-31T22:42Z[UTC]
8. Appendix
Query-DSL support
The Neo4j Cypher-DSL has some support for Query-DSL. It can
-
turn instances of
com.querydsl.core.types.Predicate
intoorg.neo4j.cypherdsl.core.Condition
, -
turn instances of
com.querydsl.core.types.Expression
intoorg.neo4j.cypherdsl.core.Expression
, -
create
org.neo4j.cypherdsl.core.Node
instances fromcom.querydsl.core.types.Path
and also -
create
org.neo4j.cypherdsl.core.SymbolicName
With this, many static meta models based on Query-DSL can be used to create Queries and match on nodes. Most operations supported by Query-DSL are translated into Cypher that is understood by Neo4j 4.0+, so that most predicates should work - at least from a syntactic point of view - out of the box.
Expressions are most useful to address properties in return statements and the like.
Here’s one example on how to use it:
QPerson n = new QPerson("n"); (1)
Statement statement = Cypher.match(Cypher.adapt(n).asNode()) (2)
.where(Cypher.adapt(n.firstName.eq("P").and(n.age.gt(25))).asCondition()) (3)
.returning(Cypher.adapt(n).asName()) (4)
.build();
assertThat(statement.getCatalog().getParameters()).isEmpty();
assertThat(statement.getCypher())
.isEqualTo("MATCH (n:`Person`) WHERE n.firstName = 'P' AND n.age > 25 RETURN n");
1 | This makes use of a "Q"-class generated by Query-DSL APT (using the general processor).
alias and class based paths works, too.
Please make sure to name the instance accordingly when you use it as node (see next step) |
2 | Adapt the "Q"-class into a node. Please note it must be named accordingly, otherwise you query won’t return the expected results |
3 | Create some Query-DSL predicate based on the properties and adapt it as condition |
4 | Return some Query-DSL properties and adapt it as expression |
The Statement
offers a way to render all constants as parameters, so that they don’t bust the query cache:
QPerson n = new QPerson("n");
Statement statement = Cypher.match(Cypher.adapt(n).asNode())
.where(Cypher.adapt(n.firstName.eq("P").and(n.age.gt(25))).asCondition())
.returning(Cypher.adapt(n).asName())
.build();
statement.setRenderConstantsAsParameters(true); (1)
assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl01", "P"); (2)
assertThat(statement.getCatalog().getParameters()).containsEntry("pcdsl02", 25);
assertThat(statement.getCypher())
.isEqualTo("MATCH (n:`Person`) WHERE n.firstName = $pcdsl01 AND n.age > $pcdsl02 RETURN n"); (3)
1 | Set this to true before accessing parameters of the statement or rendering the statement |
2 | Access the statements parameters for generated parameter names |
3 | Compare the statement to the first listening. Constants are gone now |
The Neo4j Cypher-DSL will collect all parameters defined via Query-DSL for you:
QPerson n = new QPerson("n");
Statement statement = Cypher.match(Cypher.adapt(n).asNode())
.where(Cypher.adapt(n.firstName.eq(new Param<>(String.class, "name"))
.and(n.age.gt(new Param<>(Integer.class, "age"))) (1)
).asCondition()
)
.returning(Cypher.adapt(n).asName())
.build();
assertThat(statement.getCatalog().getParameterNames()).hasSize(2); (2)
assertThat(statement.getCypher())
.isEqualTo("MATCH (n:`Person`) WHERE n.firstName = $name AND n.age > $age RETURN n");
1 | Basically the same predicate as above, but with parameters, which will be turned into correct placeholders |
2 | Access the parameter names via the Statement object |
Required dependencies
The Query-DSL support in the Neo4j Cypher-DSL is optional, and the Query-DSL dependency is only in the provided
scope.
To make use of Cypher.adapt()
, you must add the following dependency in addition to the Cypher-DSL:
Maven
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-core</artifactId>
<version>4.4.0</version>
<scope>provided</scope>
</dependency>
Gradle
dependencies {
implementation 'com.querydsl:query-dsl-core:4.4.0'
}
In case you want to use an annotation processor, you have to add additional dependencies and depending on your Java environment. We use the following in our tests:
<dependencies>
<dependency>
<groupId>com.querydsl</groupId>
<artifactId>querydsl-apt</artifactId>
<version>4.4.0</version>
<classifier>general</classifier>
</dependency>
<dependency>
<groupId>javax.annotation</groupId>
<artifactId>javax.annotation-api</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
Building the Neo4j Cypher-DSL
Requirements
For the full project, including examples and native tests:
-
GraalVM based on JDK 17: https://www.graalvm.org/downloads/
At least GraalVM 22.3.0 is required due to restrictions in org.graalvm.buildtools:native-maven-plugin .
The rationale behind that is explained here
and we don’t do apply any of the suggested workarounds as the releases are done with GraalVM 22.3.0 anyway.
|
For the project, including examples but skipping native tests
-
JDK 17+ (Can be OpenJDK or Oracle JDK)
Maven 3.8.4 is our build tool of choice. We provide the Maven wrapper, see mvnw
respectively mvnw.cmd
in the project root;
the wrapper downloads the appropriate Maven version automatically.
The build requires a local copy of the project:
$ git clone git@github.com:neo4j-contrib/cypher-dsl.git
Fast build
This is useful if you want to just have an installation of a snapshot version. No tests are run, no verification is done. |
$ ./mvnw -Dfast package
For a local install - maybe to try out a future release - you can also specify the version number:
$ ./mvnw -Dfast -Drevision=1337 -Dchangelist= install
Full build (including examples and native tests)
Before you proceed, verify your locally installed JDK version. The output should be similar:
$ java -version
openjdk version "17.0.5" 2022-10-18
OpenJDK Runtime Environment GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.0 (build 17.0.5+8-jvmci-22.3-b08, mixed mode, sharing)
Check whether GraalVM native-image
is present with:
native-image
$ gu list
ComponentId Version Component name Stability Origin
-------------------------------------------------------------------------------------------------------------------
graalvm 22.3.0 GraalVM Core Experimental
js 22.3.0 Graal.js Experimental github.com
native-image 22.3.0 Native Image Experimental github.com
You should see native-image
in the list. If not, install it via gu install native-image
.
After that, use ./mvnw
on a Unix-like operating system to build the Cypher-DSL:
$ ./mvnw clean verify
On a Windows machine, use
$ mvnw.cmd clean verify
Skipping native tests
On a plain JDK 17 or higher, run the following to skip the native tests:
$ ./mvnw clean verify -pl \!org.neo4j:neo4j-cypher-dsl-native-tests
Build only the core module
The core module can be build on plain JDK 17 with:
$ ./mvnw clean verify -am -pl org.neo4j:neo4j-cypher-dsl
CI-friendly version numbers
We use CI-friendly version numbers, the current build will always identify itself as 9999-SNAPSHOT. If you need to create a specific version you can specify the revision, the changelist and an optional hash like this:
$ ./mvnw clean package -pl org.neo4j:neo4j-cypher-dsl -Drevision=2022.1.0 -Dchangelist=-SNAPSHOT
Architecture
The Neo4j-Cypher-DSL consists of one main module: org.neo4j.cypherdsl.core
.
The coordinates of that module org.neo4j:neo4j-cypher-dsl
, the JDK module name is org.neo4j.cypherdsl.core
.
The rendering feature is part of the core module.
All other modules depend on the core. As the core reflects the Cypher language, it is not meant to be extendable. Therefore, there is little to know API to do so with the AST visitor being the exception.
We document our rules structure with ArchUnit within a single unittest named org.neo4j.cypherdsl.core.PackageAndAPIStructureTest
.
Coding rules
Consistent naming
The following naming conventions are used throughout the project:
org.neo4j.cypherdsl
.@BeforeAll
void importCorePackage() {
coreClasses = new ClassFileImporter()
.withImportOption(ImportOption.Predefined.DO_NOT_INCLUDE_TESTS)
.importPackages("org.neo4j.cypherdsl.core..");
}
API
General considerations
We use @API Guardian to keep track of what we expose as public or internal API. To keep things both clear and concise, we restrict the usage of those annotations to interfaces, classes (only public methods and constructors: and annotations.
@Test
void coreMostNotDependOnRendering() {
ArchRule rule = noClasses().that()
.resideInAPackage("..core")
.and(not(assignableFrom(AbstractStatement.class).or(assignableFrom(RendererBridge.class))))
.should().dependOnClassesThat(resideInAPackage("..renderer.."));
rule.check(coreClasses);
}
Internal API
While we are pretty clear about the intended use of our classes (being experimental, public API or strictly internal), we want to make sure that no-one can coincidentally inherit from internal classes that we couldn’t restrict to default package visibility.
There should not be any internal, public API inside the core package.
Everything that needs to be public for reasons (like being used in the renderer), must go into the internal
package.
This package won’t be exported via module-info.java
.
@Test
void internalPublicClassesMustBeFinal() {
ArchRule rule = classes().that()
.areAnnotatedWith(API.class)
.and().arePublic()
.and().areTopLevelClasses()
.and(not(modifier(JavaModifier.ABSTRACT)))
.and(new DescribedPredicate<>("Is internal API") {
@Override
public boolean test(JavaClass input) {
API.Status status = input.getAnnotationOfType(API.class).status();
return "INTERNAL".equals(status.name());
}
})
.should().haveModifier(JavaModifier.FINAL)
.andShould(ArchConditions.not(ArchConditions.resideInAPackage("..core")));
rule.check(coreClasses);
}
Structure
Neo4j-Cypher-DSL Core
The core of the Cypher-DSL is consist of a set of classes that loosely reassemble the openCypher spec, especially in the railroad diagrams.
The main package of the core module is org.neo4j.cypherdsl.core
which also reflects in the JDK module name: org.neo4j.cypherdsl.core
.
Part of the Cypher-DSL core is also the renderer
package as the main goal of the core is to render Cypher.
The renderer
package is a sub-package of core
as it is an essential part of it and in addition, the above mentioned
JDK module name should reflect exactly one package.
So while all other subpackages in core
can be used freely from the core
classes themselves, we don’t want to access the
renderer
package apart from one exception: The AbstractStatement
class can be used to invoke the rendering process without
explicitly specifying a renderer:
@Test
void coreMostNotDependOnRendering() {
ArchRule rule = noClasses().that()
.resideInAPackage("..core")
.and(not(assignableFrom(AbstractStatement.class).or(assignableFrom(RendererBridge.class))))
.should().dependOnClassesThat(resideInAPackage("..renderer.."));
rule.check(coreClasses);
}
The renderer
package is not only free to use the whole core
, it must do so to fulfill its purpose.
The ast
and utils
packages however should not have dependencies outside their own:
@ParameterizedTest
@ValueSource(strings = { "..core.ast", "..core.utils" })
void independentSupportPackages(String supportPackage) {
ArchRule rule = noClasses().that()
.resideInAPackage(supportPackage)
.should().dependOnClassesThat(
resideInAPackage("..core..").and(not(resideInAPackage(supportPackage)))
);
rule.check(coreClasses);
}
Change log
2024.2
2024.2.0
A new minor release so shortly after the last?
We changed the behaviour of the renderer when using generated names.
Before and upto including 2024.1.0 we didn’t allow aliases to be reused.
If in the original query alias x
would have been legally reused, we would not have reused them with generated names, i.e. we would have usd v0
and then v1
.
I think this is wrong, and we changed this behaviour, hence a new minor is due.
If you are using generated names, you can opt out of this behaviour like this:
var generatedNamesConfig = EnumSet.complementOf(EnumSet.of(Configuration.GeneratedNames.REUSE_ALIASES));
var renderer = Renderer.getRenderer(Configuration.newConfig()
.withGeneratedNames(generatedNamesConfig)
.build());
Apart from that, this is a drop-in replacement for 2024.1 and 2024.0.
Congratulations to @ali-ince for contributing his first feature.
And last but not least, @Andy2003 gave our AST factory and the scoping mechanism a real good test run, and we have been able to fix several bugs again. Thank you!
π Features
-
Apply sorting of maps when parsing to projections, too. (#1085)
-
Add call raw cypher to top level entry point (#1073)
π Bug Fixes
-
Donβt introduce new aliases on for each alias used. (#1084)
-
Export return variables from unions into scope proper. (#1075)
-
Correctly compute imports. (#1076)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j.version from 5.23.0 to 5.24.0 (#1092)
-
Bump com.google.guava:guava (#1095)
-
Bump com.mycila:license-maven-plugin from 4.5 to 4.6 (#1090)
-
Bump mockito.version from 5.13.0 to 5.14.0 (#1094)
-
Bump org.neo4j.driver:neo4j-java-driver (#1093)
-
Bump com.fasterxml.jackson:jackson-bom (#1091)
-
Bump com.puppycrawl.tools:checkstyle (#1089)
-
Bump org.jetbrains:annotations from 24.1.0 to 25.0.0 (#1088)
-
Bump org.junit:junit-bom from 5.11.0 to 5.11.1 (#1087)
-
Bump org.springframework.boot:spring-boot-starter-parent (#1083)
-
Bump net.java.dev.jna:jna from 5.14.0 to 5.15.0 (#1082)
-
Bump joda-time:joda-time from 2.12.7 to 2.13.0 (#1081)
-
Bump io.projectreactor:reactor-bom (#1080)
-
Bump org.springframework.data:spring-data-neo4j (#1079)
-
Bump org.graalvm.buildtools:native-maven-plugin (#1078)
-
2024.1
2024.1.0
This new minor release adds a third dialect: Neo4j 5.23, catering for Neo4j >= 5.23.
The initial release will rewrite sub-query CALL
statements with importing WITH
into sub-queries with variable scoped CALL
clause.
While the former is still available in Neo4j 5.23, it will cause deprecation warnings, hence, if you want to get rid of those, change the dialect accordingly.
No need to rewrite any query on your own.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.checkerframework:checker-qual (#1072)
-
Bump actions/download-artifact from 1 to 4.1.7 in /.github/workflows (#1070)
-
Bump org.apache.maven.plugins:maven-surefire-plugin (#1068)
-
Bump mockito.version from 5.12.0 to 5.13.0 (#1067)
-
Bump org.apache.maven.plugins:maven-failsafe-plugin (#1066)
-
Bump org.neo4j.driver:neo4j-java-driver (#1065)
-
Bump com.puppycrawl.tools:checkstyle (#1064)
-
Bump org.apache.maven.plugins:maven-javadoc-plugin (#1063)
-
Bump neo4j.version from 5.22.0 to 5.23.0 (#1053)
-
Bump org.apache.maven.plugins:maven-install-plugin (#1057)
-
Bump org.asciidoctor:asciidoctorj from 2.5.13 to 3.0.0 (#1056)
-
Bump org.springframework.boot:spring-boot-starter-parent (#1055)
-
Bump org.apache.maven.plugins:maven-deploy-plugin (#1054)
-
Bump com.puppycrawl.tools:checkstyle (#1052)
-
Bump org.apache.maven.plugins:maven-checkstyle-plugin (#1051)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1050)
-
2024.0
2024.0.3
This is mostly a release that upgrades dependency, with the noteworthy exception of having now a unified property accessor.
Thanks to @fbiville for his contribution to the documentation, @loveleif for his support and making the latest Neo4j Cypher parser and its improvements work on the module path again and to @Andy2003 for his latest suggestion of the unified property accessor.
π Documentation
-
Update latest version supporting JDK 8 in README. (#1041)
-
Fix documentation link in README (#1026)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump com.google.guava:guava (#1049)
-
Bump org.junit:junit-bom from 5.10.3 to 5.11.0 (#1048)
-
Bump org.apache.maven.plugins:maven-failsafe-plugin (#1047)
-
Bump org.codehaus.mojo:exec-maven-plugin (#1046)
-
Bump org.springframework.data:spring-data-neo4j (#1045)
-
Bump io.projectreactor:reactor-bom (#1044)
-
Bump org.apache.maven.plugins:maven-surefire-plugin (#1043)
-
Bump org.apache.maven.plugins:maven-site-plugin (#1042)
-
Bump org.codehaus.mojo:exec-maven-plugin (#1040)
-
Bump org.checkerframework:checker-qual (#1039)
-
Bump testcontainers.version from 1.20.0 to 1.20.1 (#1038)
-
Bump neo4j.version from 5.20.0 to 5.22.0 (#1036)
-
Bump org.neo4j.driver:neo4j-java-driver (#1037)
-
Bump org.springframework.boot:spring-boot-starter-parent (#1035)
-
Bump testcontainers.version from 1.19.8 to 1.20.0 (#1034)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#1033)
-
Bump org.apache.maven.plugins:maven-javadoc-plugin (#1032)
-
Bump org.apache.maven.plugins:maven-surefire-plugin (#1030)
-
Bump org.apache.maven.plugins:maven-failsafe-plugin (#1029)
-
Bump org.springframework.data:spring-data-neo4j (#1028)
-
Bump org.assertj:assertj-core from 3.26.0 to 3.26.3 (#1027)
-
2024.0.2
π The big party release! π
With this release we move the repository from github.com/neo4j-contrib to github.com/neo4j with Neo4j adding Cypher-DSL to the list of supported modules. What we have now on our todo list is to incorporate our documentation into the official Neo4j docs, but apart from that, little will change immediate. Even our Maven coordinates will stay the same. You can however rely on the fact that Cypher-DSL is not going anywhere anytime soon.
Thanks to @stumoore for supporting this!
π§Ή Housekeeping
-
Dependency upgrades
-
Bump io.projectreactor:reactor-bom (#1025)
-
Bump org.checkerframework:checker-qual (#1024)
-
Bump com.fasterxml.jackson:jackson-bom (#1023)
-
Bump org.moditect:moditect-maven-plugin (#1019)
-
Bump org.asciidoctor:asciidoctorj-diagram (#1021)
-
Bump org.junit:junit-bom from 5.10.2 to 5.10.3 (#1020)
-
Bump org.neo4j.driver:neo4j-java-driver (#1018)
-
Bump org.springframework.boot:spring-boot-starter-parent (#1016)
-
Bump org.apache.maven.plugins:maven-jar-plugin (#1015)
-
2024.0.1
This is a pure bug-fix release. Thanks to @Andy2003 for spotting yet another scoping issue.
2024.0.0
We’re finally going 2024 with this release.
The biggest new feature in this release is that we now allow chaining statements that end with a YIELD
clause, which lets you compose complex queries in a nicer way.
We also removed all deprecated constructs and methods we accumulated until now. If you ignored the warnings until now, you cannot do any longer. The latest SDN release is prepared for this Cypher-DSL release already, as we did the necessary changes over there already (See this commit for the necessary changes for example).
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.apache.maven.plugins:maven-shade-plugin (#1006)
-
Bump org.neo4j.driver:neo4j-java-driver (#1005)
-
Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#1004)
-
Bump com.google.guava:guava (#1003)
-
Bump org.apache.maven.plugins:maven-enforcer-plugin (#1002)
-
Bump org.apache.maven.plugins:maven-javadoc-plugin (#1001)
-
Bump com.github.ekryd.sortpom:sortpom-maven-plugin (#992)
-
Bump org.assertj:assertj-core from 3.25.3 to 3.26.0 (#998)
-
Bump org.springframework.boot:spring-boot-starter-parent (#997)
-
Bump com.puppycrawl.tools:checkstyle (#996)
-
Bump org.codehaus.mojo:exec-maven-plugin (#995)
-
Bump org.asciidoctor:asciidoctorj from 2.5.12 to 2.5.13 (#993)
-
Bump org.springframework.data:spring-data-neo4j (#991)
-
Bump io.projectreactor:reactor-bom (#990)
-
Bump org.graalvm.buildtools:native-maven-plugin (#989)
-
Bump com.mycila:license-maven-plugin from 4.3 to 4.5 (#987)
-
Bump mockito.version from 5.11.0 to 5.12.0 (#986)
-
Bump testcontainers.version from 1.19.7 to 1.19.8 (#985)
-
Bump com.google.guava:guava (#982)
-
Bump org.checkerframework:checker-qual (#984)
-
Bump org.apache.maven.plugins:maven-install-plugin (#983)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#981)
-
Bump com.fasterxml.jackson:jackson-bom (#980)
-
Bump org.apache.maven.plugins:maven-deploy-plugin (#979)
-
2023.9
2023.9.7
Not everything goes as planned ;) Another 2023.9 release, enjoy.
π Bug Fixes
-
Use proper generics when generating extensible models. (#974)
-
Treat the asterisk correctly in an importing with. (#973)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.neo4j.driver:neo4j-java-driver (#978)
-
Bump org.apache.maven.plugins:maven-shade-plugin (#977)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#976)
-
Bump com.puppycrawl.tools:checkstyle (#975)
-
Bump neo4j.version from 5.18.1 to 5.19.0 (#965)
-
Bump com.tngtech.archunit:archunit from 1.2.1 to 1.3.0 (#966)
-
Bump org.springframework.data:spring-data-neo4j (#967)
-
Bump io.projectreactor:reactor-bom (#968)
-
Bump org.springframework.boot:spring-boot-starter-parent (#971)
-
Bump org.apache.maven.plugins:maven-jar-plugin (#972)
-
2023.9.6
Heads up this is the last planned release in the 2023.x series.
The next release will be 2024.0.0, in which all deprecations apart from internalId
on nodes and relationships will be removed.
With that change, the Cypher-DSL will have one single entry point for all operations: org.neo4j.cypherdsl.core.Cypher
.
Nothing will change in terms of JDK compatibility. Cypher-DSL 2024 will still require JDK 17, and will run just fine on JDK 21 and higher.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.jacoco:jacoco-maven-plugin from 0.8.11 to 0.8.12 (#960)
-
Bump org.neo4j.driver:neo4j-java-driver (#959)
-
Bump org.apache.maven.plugins:maven-source-plugin (#958)
-
Bump com.puppycrawl.tools:checkstyle (#957)
-
Bump org.moditect:moditect-maven-plugin (#956)
-
Bump neo4j.version from 5.18.0 to 5.18.1 (#955)
-
Bump org.ow2.asm:asm from 9.6 to 9.7 (#954)
-
Bump org.springframework.boot:spring-boot-starter-parent (#953)
-
2023.9.5
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.springframework.data:spring-data-neo4j
-
Bump neo4j.version from 5.17.0 to 5.18.0 (#944)
-
Bump com.google.guava:guava (#952)
-
Bump io.projectreactor:reactor-bom (#951)
-
Bump org.asciidoctor:asciidoctorj from 2.5.11 to 2.5.12 (#950)
-
Bump com.fasterxml.jackson:jackson-bom (#949)
-
Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#947)
-
Bump org.moditect:moditect-maven-plugin (#946)
-
Bump com.puppycrawl.tools:checkstyle (#945)
-
Bump testcontainers.version from 1.19.6 to 1.19.7 (#942)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#941)
-
Bump com.fasterxml.jackson:jackson-bom (#940)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#938)
-
Bump org.neo4j.driver:neo4j-java-driver (#937)
-
Bump com.puppycrawl.tools:checkstyle (#936)
-
Bump mockito.version from 5.10.0 to 5.11.0 (#935)
-
2023.9.4
Change parser license to The Apache Software License, Version 2.0 (same as the Neo4j JavaCC based parser, which we use under the hoods).
Thanks a lot @hindog, @fbiville and @Andy2003 for agreeing to relicense your contributions, too.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j.version from 5.16.0 to 5.17.0 (#933)
-
Bump testcontainers.version from 1.19.5 to 1.19.6 (#934)
-
Bump org.apache.maven.plugins:maven-shade-plugin (#932)
-
Bump org.springframework.boot:spring-boot-starter-parent (#931)
-
Bump org.graalvm.buildtools:native-maven-plugin (#930)
-
Bump org.codehaus.mojo:exec-maven-plugin (#929)
-
Bump org.asciidoctor:asciidoctor-maven-plugin (#927)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#928)
-
Bump org.springframework.data:spring-data-neo4j (#926)
-
Bump io.projectreactor:reactor-bom (#925)
-
Bump org.asciidoctor:asciidoctorj-diagram (#924)
-
Bump org.graalvm.buildtools:native-maven-plugin (#919)
-
Bump org.assertj:assertj-core from 3.25.2 to 3.25.3 (#918)
-
Bump org.asciidoctor:asciidoctorj-diagram (#916)
-
Bump org.junit:junit-bom from 5.10.1 to 5.10.2 (#915)
-
Bump testcontainers.version from 1.19.4 to 1.19.5 (#923)
-
Bump org.asciidoctor:asciidoctor-maven-plugin (#922)
-
Bump org.neo4j.driver:neo4j-java-driver (#920)
-
Bump joda-time:joda-time from 2.12.6 to 2.12.7 (#917)
-
2023.9.3
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j.version from 5.15.0 to 5.16.0 (#910)
-
Bump org.assertj:assertj-core from 3.25.1 to 3.25.2 (#914)
-
Bump com.querydsl:querydsl-core from 5.0.0 to 5.1.0 (#913)
-
Bump mockito.version from 5.9.0 to 5.10.0 (#912)
-
Bump com.puppycrawl.tools:checkstyle (#911)
-
Bump testcontainers.version from 1.19.3 to 1.19.4 (#909)
-
Bump org.springframework.boot:spring-boot-starter-parent (#904)
-
2023.9.2
Please read the updated stance wrt calver/semver in the README. This release is current and the first one in 2024, including some new, additive and non-breaking features contributed by @Andy2003
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.asciidoctor:asciidoctor-maven-plugin (#902)
-
Bump org.apache.maven.plugins:maven-failsafe-plugin (#901)
-
Bump mockito.version from 5.8.0 to 5.9.0 (#900)
-
Bump org.codehaus.mojo:flatten-maven-plugin (#899)
-
Bump org.springframework.data:spring-data-neo4j (#898)
-
Bump io.projectreactor:reactor-bom (#897)
-
Bump org.apache.maven.plugins:maven-surefire-plugin (#896)
-
Bump org.assertj:assertj-core from 3.25.0 to 3.25.1 (#893)
-
Bump org.neo4j.driver:neo4j-java-driver (#892)
-
Bump joda-time:joda-time from 2.12.5 to 2.12.6 (#891)
-
Bump org.asciidoctor:asciidoctorj-diagram (#890)
-
Bump com.fasterxml.jackson:jackson-bom (#887)
-
Bump org.assertj:assertj-core from 3.24.2 to 3.25.0 (#889)
-
Bump com.puppycrawl.tools:checkstyle (#888)
-
Bump org.asciidoctor:asciidoctorj from 2.5.10 to 2.5.11 (#886)
-
Bump com.google.guava:guava (#885)
-
Bump org.springframework.boot:spring-boot-starter-parent (#884)
-
2023.9.1
π Bug Fixes
-
for #840 add missing casts in constructor super calls for relations with generic start or / and end node (#866)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j.version from 5.14.0 to 5.15.0 (#880)
-
Bump org.checkerframework:checker-qual (#883)
-
Bump io.projectreactor:reactor-bom (#882)
-
Bump org.springframework.data:spring-data-neo4j (#881)
-
Bump org.apache.maven.plugins:maven-surefire-plugin (#879)
-
Bump org.apache.maven.plugins:maven-failsafe-plugin (#878)
-
Bump com.puppycrawl.tools:checkstyle (#876)
-
Bump net.java.dev.jna:jna from 5.13.0 to 5.14.0 (#877)
-
Bump org.checkerframework:checker-qual (#875)
-
Bump org.apache.maven.plugins:maven-javadoc-plugin (#874)
-
Bump org.neo4j.driver:neo4j-java-driver (#873)
-
Bump com.tngtech.archunit:archunit from 1.2.0 to 1.2.1 (#872)
-
Bump mockito.version from 5.7.0 to 5.8.0 (#871)
-
Bump neo4j.version from 5.13.0 to 5.14.0 (#868)
-
Bump testcontainers.version from 1.19.2 to 1.19.3 (#867)
-
2023.9.0
2023.9 contains several new features: It brings support for parsing and rendering Quantified Path Patterns (QPP), shifts to a single, easy to find main entry point to the DSL via just Cypher
and makes the static code generator a bit more powerful.
While QPP are a powerful feature (have a look at "Getting From Denmark Hill to Gatwick Airport With Quantified Path Patterns") to see what you can do with them, I find them hard to read, with all the parentheses and I did not expect them to really fit in well with our builder. However, it turned out that the elements we need to provide in our own AST to render what we parsed do work well: If you decide to build QPP with Cypher-DSL, you can now quantify relationship patterns as a whole or only the relationship, making up already for many uses cases.
The single entry point to our API makes the whole system a lot more discoverable.
@lukaseder did create a ticket for that in the beginning of 2023 and if someone knows the importance of that, he is that someone as the creator of jOOQ.
Thank you, Lukas and of course earlier this week, @Andy2003 for actually doing the work of adding all those methods to Cypher
.
If you don’t care about deprecation warnings, 2023.9.0 will be a drop-in replacement. The existing entry points won’t go away until the next major release, in which they will be made package private. Until then, they are deprecated.
It my sound like a broken record by now, but again: Thank you, @zakjan and @ikwattro for your input on QPP, now we are waiting for your bug-reports.
π Features
-
Provide a single DSL API entry point. (#862)
-
Allow parsing of
collect
expression. (#861) -
Add support for quantified path patterns. (#860)
-
Add support for predicates inside pattern elements. (#859)
-
Add ability to add additional factory methods for relationship models to a node in the static model (#840)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump spring-boot-starter-parent from 3.1.5 to 3.2.0
-
Bump auto-common to 1.2.2
-
Bump errorprone from 2.12.1 to 2.23.0
-
Bump sortpom from 2.15.0 to 3.3.0
-
Bump com.opencsv:opencsv from 5.8 to 5.9
-
Bump testcontainers.version from 1.19.2 to 1.19.3
-
Bump testcontainers.version from 1.19.1 to 1.19.2 (#857)
-
Bump org.codehaus.mojo:exec-maven-plugin (#856)
-
Bump io.projectreactor:reactor-bom (#855)
-
Bump com.puppycrawl.tools:checkstyle (#854)
-
Bump com.fasterxml.jackson:jackson-bom (#853)
-
Bump org.jetbrains:annotations from 24.0.1 to 24.1.0 (#852)
-
Bump org.springframework.data:spring-data-neo4j (#851)
-
Bump org.apache.maven.plugins:maven-surefire-plugin (#850)
-
Bump org.apache.maven.plugins:maven-failsafe-plugin (#849)
-
Bump org.apache.maven.plugins:maven-javadoc-plugin (#848)
-
2023.8
2023.8.1
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.moditect:moditect-maven-plugin (#843)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#845)
-
Bump com.tngtech.archunit:archunit from 1.1.0 to 1.2.0 (#846)
-
Bump org.checkerframework:checker-qual (#844)
-
Bump mockito.version from 5.6.0 to 5.7.0 (#842)
-
Bump org.junit:junit-bom from 5.10.0 to 5.10.1 (#841)
-
2023.8.0
This minor release is drop-in compatible with 2023.7, but it adds support for using COLLECT {}
sub-queries, which required enhancing some interfaces (that only we should implement, but still, it’s a minor upgrade then).
The price for finding the most bugs in the scoping strategy applied for sub-queries in this release goes to @Andy2003, thank you!
π Features
-
Add support for
COLLECT
subqueries. (#831) -
Make fieldname generator configurable. (#830)
π Bug Fixes
-
Make sure local scope is cleared after leaving subquery expressions. (#837)
-
Recognize entities defined in sub-queries correctly. (#827)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump org.apache.maven.plugins:maven-surefire-plugin (#836)
-
Bump org.apache.maven.plugins:maven-failsafe-plugin (#835)
-
Bump org.apache.maven.plugins:maven-checkstyle-plugin (#834)
-
Bump org.neo4j.driver:neo4j-java-driver (#833)
-
Bump org.neo4j:neo4j-cypher-javacc-parser from 5.12.0 to 5.13.0 (#821)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#823)
-
Bump org.graalvm.buildtools:native-maven-plugin (#824)
-
Bump org.jacoco:jacoco-maven-plugin from 0.8.10 to 0.8.11 (#822)
-
Bump org.springframework.boot:spring-boot-starter-parent (#820)
-
2023.7
2023.7.1
Thanks to @jrsperry for a great bug-report and the fix for includesAll
and includesAny
.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump mockito.version from 5.5.0 to 5.6.0 (#812)
-
Bump org.springframework.data:spring-data-neo4j (#817)
-
Bump com.google.guava:guava (#816)
-
Bump org.checkerframework:checker-qual (#809)
-
Bump net.bytebuddy:byte-buddy-parent from 1.14.8 to 1.14.9 (#818)
-
Bump io.projectreactor:reactor-bom (#815)
-
Bump com.fasterxml.jackson:jackson-bom (#814)
-
Bump org.neo4j.driver:neo4j-java-driver (#811)
-
Bump testcontainers.version from 1.19.0 to 1.19.1 (#810)
-
Bump com.puppycrawl.tools:checkstyle (#807)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#808)
-
Bump org.ow2.asm:asm from 9.5 to 9.6 (#806)
-
Bump com.mycila:license-maven-plugin from 4.2.rc2 to 4.3 (#805)
-
Bump org.springframework.boot:spring-boot-starter-parent (#804)
-
Bump org.apache.maven.plugins:maven-shade-plugin (#803)
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#802)
-
Bump org.graalvm.buildtools:native-maven-plugin (#800)
-
Bump io.projectreactor:reactor-bom (#799)
-
Bump org.neo4j:neo4j-cypher-javacc-parser (#798)
-
Bump org.springframework.data:spring-data-neo4j (#797)
-
Bump org.sonarsource.scanner.maven:sonar-maven-plugin (#796)
-
Bump org.asciidoctor:asciidoctorj-diagram (#795)
-
Bump org.apache.maven.plugins:maven-javadoc-plugin (#794)
-
Bump com.opencsv:opencsv from 5.7.1 to 5.8 (#793)
-
Bump org.apache.maven.plugins:maven-enforcer-plugin (#792)
-
Bump org.graalvm.buildtools:native-maven-plugin (#791)
-
2023.6
2023.6.1
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump com.github.siom79.japicmp:japicmp-maven-plugin (#788)
-
Bump org.neo4j.driver:neo4j-java-driver (#787)
-
Bump org.checkerframework:checker-qual (#786)
-
Bump org.springframework.boot:spring-boot-starter-parent (#783)
-
Bump com.puppycrawl.tools:checkstyle (#782)
-
Bump mockito.version from 5.4.0 to 5.5.0 (#781)
-
Bump org.graalvm.buildtools:native-maven-plugin (#780)
-
Bump testcontainers.version from 1.18.3 to 1.19.0 (#779)
-
Bump org.apache.maven.plugins:maven-enforcer-plugin (#778)
-
Bump org.springframework.data:spring-data-neo4j (#775)
-
Bump org.neo4j:neo4j-cypher-javacc-parser (#774)
-
Bump io.projectreactor:reactor-bom (#773)
-
Bump com.tngtech.archunit:archunit from 1.0.1 to 1.1.0 (#772)
-
Bump org.asciidoctor:asciidoctorj-diagram (#771)
-
Bump org.graalvm.buildtools:native-maven-plugin (#770)
-
Thanks to @zakjan for a great bug-report again and ofc @ikwattro for your ongoing support and feedback.
2023.6.0
π Features
-
Add callbacks for function and procedure invocations. (#764, #758, also backported as 2022.9.0, thanks to @ClemDoum for your input here)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump com.google.guava:guava (#769)
-
Bump org.checkerframework:checker-qual (#768)
-
Bump com.puppycrawl.tools:checkstyle (#767)
-
Bump org.asciidoctor:asciidoctorj-diagram (#766)
-
Bump org.neo4j.driver:neo4j-java-driver (#765)
-
Bump org.neo4j:neo4j-cypher-javacc-parser from 5.9.0 to 5.10.0 (#761)
-
Bump checker-qual from 3.35.0 to 3.36.0 (#757)
-
Bump spring-data-neo4j from 7.1.1 to 7.1.2 (#759)
-
Bump reactor-bom from 2022.0.8 to 2022.0.9 (#760)
-
Bump org.junit:junit-bom from 5.9.3 to 5.10.0 (#762)
-
Bump org.springframework.boot:spring-boot-starter-parent (#763)
-
2023.5
2023.5.0
Wait what, another minor? Yes, we added new methods to some builders for #753 and a new method in #756 that takes in the direction of a relationship. While these are not interfaces for you to implemented and the methods are defaulted, semver requires a minor bump nevertheless.
Thanks to @israelstmz and ss with almost every release this year, to @ikwattro, for your input!
2023.4
2023.4.0
2023.4.0 comes with a whole list of new features.
As we deprecated two things (the DEFAULT
dialect and org.neo4j.cypherdsl.core.Cypher.use(org.neo4j.cypherdsl.core.SymbolicName, org.neo4j.cypherdsl.core.Statement)
), your project might break depending on your warning settings. The DEFAULT
dialect is now org.neo4j.cypherdsl.core.renderer.Dialect.NEO4J_4
(which keeps on being the default) and the use
method has a new overload taking in an expression. You might need to explicitly cast here until we remove the deprecated method for good. This change was necessary to be able to put all functions in the graph.*
namespace to use.
Thanks to our reporters, contributors and supporters @xdelox, @ikwattro, @nmervaillie and Rohan Kharwar.
π Features
-
Introduce event to capture parsed literals. (#742)
-
Allow retrieval of literals. (#741)
-
Add builder methods for
FOREACH
. (#740) -
Provide all functions in the
graph.*
namespaces. (#734) -
Provide a way to fill parsed parameters with values. (#732)
ποΈ Refactorings
-
Replace
DEFAULT
dialect with explicitNEO4J_4
dialect. (#736) -
Use sorted sets everywhere to keep orders of identifiables in the catalog close to users expectations. (#733)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j-cypher-javacc-parser from 5.8.0 to 5.9.0 (#743)
-
Bump reactor-bom from 2022.0.7 to 2022.0.8 (#748)
-
Bump mockito.version from 5.3.1 to 5.4.0 (#747)
-
Bump spring-data-neo4j from 7.1.0 to 7.1.1 (#746)
-
Bump native-maven-plugin from 0.9.22 to 0.9.23 (#745)
-
Bump maven-shade-plugin from 3.4.1 to 3.5.0 (#744)
-
Bump guava from 32.0.0-jre to 32.0.1-jre (#726)
-
Bump maven-surefire-plugin from 3.1.0 to 3.1.2 (#725)
-
Bump maven-failsafe-plugin from 3.1.0 to 3.1.2 (#724)
-
Bump asciidoctorj-diagram from 2.2.8 to 2.2.9 (#723)
-
Bump jackson-bom from 2.15.1 to 2.15.2 (#722)
-
Bump testcontainers.version from 1.18.1 to 1.18.3 (#721)
-
Bump checker-qual from 3.34.0 to 3.35.0 (#720)
-
Bump asciidoctorj from 2.5.8 to 2.5.10 (#719)
-
2023.3
2023.3.2
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump spring-boot-starter-parent from 3.0.6 to 3.1.0 (#711)
-
Bump neo4j-cypher-javacc-parser from 5.7.0 to 5.8.0 (#709)
-
Bump guava from 31.1-jre to 32.0.0-jre (#714)
-
Bump checkstyle from 10.11.0 to 10.12.0 (#717)
-
Bump asciidoctor-maven-plugin from 2.2.3 to 2.2.4 (#716)
-
Bump maven-checkstyle-plugin from 3.2.2 to 3.3.0 (#715)
-
Bump neo4j-java-driver from 5.8.0 to 5.9.0 (#713)
-
Bump jackson-bom from 2.15.0 to 2.15.1 (#710)
-
Bump maven-source-plugin from 3.2.1 to 3.3.0 (#708)
-
Bump testcontainers.version from 1.18.0 to 1.18.1 (#704)
-
Bump native-maven-plugin from 0.9.21 to 0.9.22 (#707)
-
Bump reactor-bom from 2022.0.6 to 2022.0.7 (#706)
-
Bump asciidoctorj-diagram from 2.2.7 to 2.2.8 (#705)
-
Bump checkstyle from 10.10.0 to 10.11.0 (#703)
-
Bump flatten-maven-plugin from 1.4.1 to 1.5.0 (#702)
-
Bump spring-data-neo4j from 7.0.5 to 7.1.0 (#701)
-
2023.3.1
π Documentation
-
Add an example combining existential sub-queries and custom procedure calls (#694)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump maven-failsafe-plugin from 3.0.0 to 3.1.0 (#698)
-
Bump checker-qual from 3.33.0 to 3.34.0 (#697)
-
Bump maven-surefire-plugin from 3.0.0 to 3.1.0 (#696)
-
Bump moditect-maven-plugin from 1.0.0.RC3 to 1.0.0.Final (#695)
-
Bump checkstyle from 10.9.3 to 10.10.0 (#692)
-
Bump junit-bom from 5.9.2 to 5.9.3 (#691)
-
Bump neo4j-java-driver from 5.7.0 to 5.8.0 (#690)
-
Bump jacoco-maven-plugin from 0.8.9 to 0.8.10 (#689)
-
Bump neo4j-cypher-javacc-parser from 5.6.0 to 5.7.0 (#685)
-
Bump jackson-bom from 2.14.2 to 2.15.0 (#688)
-
Bump spring-boot-starter-parent from 3.0.5 to 3.0.6 (#687)
-
Bump maven-checkstyle-plugin from 3.2.1 to 3.2.2 (#686)
-
Bump mockito.version from 5.3.0 to 5.3.1 (#684)
-
2023.3.0
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump native-maven-plugin from 0.9.20 to 0.9.21 (#683)
-
Bump reactor-bom from 2022.0.5 to 2022.0.6 (#682)
-
Bump asciidoctorj from 2.5.7 to 2.5.8 (#681)
-
Bump mockito.version from 5.2.0 to 5.3.0 (#680)
-
Bump spring-data-neo4j from 7.0.4 to 7.0.5 (#679)
-
Bump jacoco-maven-plugin from 0.8.8 to 0.8.9 (#672)
-
Bump testcontainers.version from 1.17.6 to 1.18.0 (#671)
-
Bump maven-enforcer-plugin from 3.2.1 to 3.3.0 (#673)
-
Bump asciidoctorj-diagram from 2.2.4 to 2.2.7 (#670)
-
Bump checker-qual from 3.32.0 to 3.33.0 (#669)
-
Bump flatten-maven-plugin from 1.4.0 to 1.4.1 (#668)
-
Bump joda-time from 2.12.4 to 2.12.5 (#667)
-
Bump neo4j-java-driver from 5.6.0 to 5.7.0 (#666)
-
2023.2
2023.2.1
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j-cypher-javacc-parser from 5.5.0 to 5.6.0 (#657)
-
Bump maven-install-plugin from 3.1.0 to 3.1.1 (#664)
-
Bump joda-time from 2.12.2 to 2.12.4 (#663)
-
Bump spring-boot-starter-parent from 3.0.4 to 3.0.5 (#662)
-
Bump asm from 9.4 to 9.5 (#661)
-
Bump checkstyle from 10.9.2 to 10.9.3 (#660)
-
Bump maven-deploy-plugin from 3.1.0 to 3.1.1 (#659)
-
Bump spring-data-neo4j from 7.0.3 to 7.0.4 (#658)
-
Bump maven-resources-plugin from 3.3.0 to 3.3.1 (#656)
-
2023.2.0
Thanks to @Andy2003 for his input on the 2023.2.0 release. The main topic of this release is adding support for semantic comparison. The Cypher-DSLs builder method creates an AST, so that is in theory an excellent and doable request. The AST has originally been created as good enough means of rendering Cypher proper when we invented the Cypher-DSL for Spring Data Neo4j 6 back in 2019. Good enough here means that it has optimizing potential and a full-blown analysis is - at least at the moment - out of scope.
Instead, we went with another approach: We added ways of normalizing
-
Variable names (identifiers for entities (nodes and relationships) and variables of lists and map comprehensions)
-
Parameter names
-
Aliases
into generated names and optionally make the parsing of literal Cypher maps constant (maps sorted alphabetically and not by order of key appearance).
This allows now for a "poor man’s" semantic comparison. Imagine 2 Cypher-DSL Statement
objects that you either created using the builder or parsed through our parser module. You can compare them like this:
static boolean areSemanticallyEquivalent(Statement statement1, Map<String, Object> args1, Statement statement2, Map<String, Object> args2) {
if (!areSemanticallyEquivalent(statement1, statement2)) {
return false;
}
var mapping1 = statement1.getCatalog().getRenamedParameters();
var mapping2 = statement2.getCatalog().getRenamedParameters();
for (Map.Entry<String, String> entry : mapping1.entrySet()) {
String key1 = entry.getKey();
String mapped = entry.getValue();
String key2 = mapping2.entrySet().stream().filter(e -> e.getValue().equals(mapped))
.map(Map.Entry::getKey).findFirst().orElseThrow();
if (!args1.get(key1).equals(args2.get(key2))) {
return false;
}
}
return true;
}
The catalog featured added in 2023.1.0 has been enhanced so that it can return now the mapping of the renamed parameters as well, allowing for inspection of parameters from different sources.
Also thanks to @hindog for contributing map literals in #642 and to @sathishkumar294 for inspiring the new dedicated overloads for type
and labels
that now work with symbolic names, too.
π Features
-
Allow map literals to be parsed into sorted maps. (#644)
-
Add support for Map literals. (#642)
-
Use generated names for aliases too if possible. (#640)
-
Make the
Asterisk
proper identifiable. (#641) -
Add
Cypher.withAll
to create a with clause importing all (*
) variables. (#639) -
Add overloads of
Functions.type
andFunctions.labels
taking in a symbolic name. (#633) -
Add extended meta data and the ability to use generated variables. (#631)
ποΈ Refactorings
-
Replace identifiers in list / pattern comprehensions, too. (#647)
-
Use scope for generated names. (#646)
-
Some general housekeeping. (#643 and #632)
-
Optimize structure of
UNWIND
.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump checkstyle from 10.8.1 to 10.9.2 (#653)
-
Bump reactor-bom from 2022.0.4 to 2022.0.5 (#652)
-
Bump asciidoctor-maven-plugin from 2.2.2 to 2.2.3 (#651)
-
Bump maven-failsafe-plugin from 3.0.0-M9 to 3.0.0 (#650)
-
Bump maven-surefire-plugin from 3.0.0-M9 to 3.0.0 (#649)
-
Bump flatten-maven-plugin from 1.3.0 to 1.4.0 (#648)
-
Bump moditect-maven-plugin from 1.0.0.RC2 to 1.0.0.RC3 (#637)
-
Bump checkstyle from 10.8.0 to 10.8.1 (#638)
-
Bump mockito.version from 5.1.1 to 5.2.0 (#636)
-
Bump spring-boot-starter-parent from 3.0.3 to 3.0.4 (#629)
-
Bump annotations from 24.0.0 to 24.0.1 (#628)
-
Bump checker-qual from 3.31.0 to 3.32.0 (#627)
-
Bump spring-data-neo4j from 7.0.2 to 7.0.3 (#626)
-
Bump reactor-bom from 2022.0.3 to 2022.0.4 (#625)
-
Bump japicmp-maven-plugin from 0.17.1 to 0.17.2 (#624)
-
2023.1
2023.1.0
2023.1.0 is the first feat release after 2023.0.0 and contains several ideas and improvements that stem from sql2cypher and from input by @lukaseder. Thank you!
Additionally, we worked again with @ikwattro from Graph Aware on building the catalog feature. Each statement - regardless of being built or parsed with the Cypher-DSL - can be analyzed via it’s catalog now. The catalog will contain labels, types and properties used in a statement and the filters created based on those tokens. You can access the catalog like this:
var input = """
MATCH (m:`Movie` {title: 'The Matrix'})<-[a:`ACTED_IN`]-(p:`Person`)
WHERE p.born >= $born
RETURN p
""";
var statement = CypherParser.parse(input);
var catalog = statement.getCatalog();
Also: All AST elements will now render themselves to Cypher-Fragments when used in toString()
scenarios. Apart from that, all bug fixes and dependency upgrades from 2022.8.5 and 2023.0.4 are included:
π Features
-
Add overloads for
count
andexists
taking in a statement and optional imports. (#623) -
Add label existences conditions to catalog. (#622)
-
Provide a catalog for identifiable items in a statement. (#617)
-
Allow retrieving parameter names
-
Add missing string functions. (#584)
-
Add support for rewriting the
MATCH
clause after parsing. (#580) -
Add
length()
function. (#569) -
Allow direct rendering of
Visitable
objects. (#554)
π Bug Fixes
-
Correctly shadow visible nodes in a subquery. (#616)
-
Parse Node pattern predicates correctly. (#615)
-
Ensure getting the type of relationships without type is safe.
-
Apply prefixes after potential separator. (#606)
-
Resolve symbolic names when looking for visited items. (#602)
-
Open implicit scope when entering a
UNION
clause. (#590) -
Move resolved symbolic names into
StatementContext
. (#586) -
Add support for label expressions. (#583)
-
Correctly track identifiable elements. (#579)
ποΈ Refactorings
-
Replace
requires static transitive
withrequires static
. -
Allow covariant collection overloads for
PatternElement
andExpression
where sensible. (#566)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j-java-driver from 5.5.0 to 5.6.0 (#621)
-
Bump spring-boot-starter-parent from 3.0.2 to 3.0.3 (#619)
-
Bump checkstyle from 10.7.0 to 10.8.0 (#620)
-
Bump spring-data-neo4j from 7.0.1 to 7.0.2 (#614)
-
Bump maven-surefire-plugin from 3.0.0-M8 to 3.0.0-M9 (#613)
-
Bump maven-failsafe-plugin from 3.0.0-M8 to 3.0.0-M9 (#612)
-
Bump checker-qual from 3.30.0 to 3.31.0 (#611)
-
Bump reactor-bom from 2022.0.2 to 2022.0.3 (#610)
-
Bump native-maven-plugin from 0.9.19 to 0.9.20 (#608)
-
Bump maven-javadoc-plugin from 3.4.1 to 3.5.0 (#607)
-
Bump neo4j-cypher-javacc-parser from 5.4.0 to 5.5.0 (#609)
-
Bump maven-deploy-plugin from 3.0.0 to 3.1.0 (#603)
-
Bump checker-qual from 3.29.0 to 3.30.0 (#601)
-
Bump maven-enforcer-plugin from 3.1.0 to 3.2.1 (#600)
-
Bump mockito.version from 5.0.0 to 5.1.1 (#599)
-
Bump checkstyle from 10.6.0 to 10.7.0 (#598)
-
Bump asciidoctorj-diagram from 2.2.3 to 2.2.4 (#597)
-
Bump jackson-bom from 2.14.1 to 2.14.2 (#594)
-
Bump neo4j-java-driver from 5.4.0 to 5.5.0 (#592)
-
Bump neo4j-cypher-javacc-parser from 5.3.0 to 5.4.0 (#593)
-
Bump spring-boot-starter-parent from 3.0.1 to 3.0.2 (#577)
-
Bump assertj-core from 3.24.1 to 3.24.2 (#576)
-
Bump maven-checkstyle-plugin from 3.2.0 to 3.2.1 (#564)
-
Bump junit-bom from 5.9.1 to 5.9.2 (#563)
-
Bump maven-failsafe-plugin from 3.0.0-M7 to 3.0.0-M8 (#560)
-
Bump reactor-bom from 2022.0.1 to 2022.0.2 (#559)
-
Bump mockito.version from 4.11.0 to 5.0.0 (#558)
-
Bump annotations from 23.1.0 to 24.0.0 (#557)
-
Bump jna from 5.12.1 to 5.13.0 (#556)
-
Bump maven-surefire-plugin from 3.0.0-M7 to 3.0.0-M8 (#555)
-
Bump spring-data-neo4j from 7.0.0 to 7.0.1 (#562)
-
Bump neo4j-java-driver from 5.3.1 to 5.4.0 (#561)
-
2023.0
2023.0.4
2023.0.4 is a bug fix release and fully compatible with 2023.0.3.
π Bug Fixes
-
Correctly shadow visible nodes in a subquery. (#616)
-
Parse Node pattern predicates correctly. (#615)
-
Ensure getting the type of relationships without type is safe.
-
Apply prefixes after potential separator. (#606)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j-java-driver from 5.5.0 to 5.6.0 (#621)
-
Bump spring-boot-starter-parent from 3.0.2 to 3.0.3 (#619)
-
Bump checkstyle from 10.7.0 to 10.8.0 (#620)
-
Bump spring-data-neo4j from 7.0.0 to 7.0.2 (#614)
-
Bump maven-surefire-plugin from 3.0.0-M8 to 3.0.0-M9 (#613)
-
Bump maven-failsafe-plugin from 3.0.0-M8 to 3.0.0-M9 (#612)
-
Bump checker-qual from 3.30.0 to 3.31.0 (#611)
-
Bump reactor-bom from 2022.0.1 to 2022.0.3 (#610)
-
Bump native-maven-plugin from 0.9.19 to 0.9.20 (#608)
-
Bump maven-javadoc-plugin from 3.4.1 to 3.5.0 (#607)
-
Bump neo4j-cypher-javacc-parser from 5.4.0 to 5.5.0 (#609)
-
2023.0.3
Thanks to @Andy2003 for their input on several bugs!
π Bug Fixes
-
Resolve symbolic names when looking for visited items. (#602)
-
Open implicit scope when entering a
UNION
clause. (#590) -
Move resolved symbolic names into
StatementContext
. (#588)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump maven-surefire-plugin from 3.0.0-M7 to 3.0.0-M8 (#555)
-
Bump maven-failsafe-plugin from 3.0.0-M7 to 3.0.0-M8 (#560)
-
Bump checker-qual from 3.29.0 to 3.30.0 (#601)
-
Bump maven-enforcer-plugin from 3.1.0 to 3.2.1 (#600)
-
Bump mockito.version from 4.11.0 to 5.1.1 (#599)
-
Bump checkstyle from 10.6.0 to 10.7.0 (#598)
-
Bump asciidoctorj-diagram from 2.2.3 to 2.2.4 (#597)
-
Bump jackson-bom from 2.14.1 to 2.14.2 (#594)
-
Bump neo4j-java-driver from 5.3.1 to 5.5.0 (#592)
-
Bump neo4j-cypher-javacc-parser from 5.3.0 to 5.4.0 (#593)
-
2023.0.2
Thanks to @ikwattro, @lukaseder and @bonelli for their input!
π Features
-
Add missing string functions. (#584)
-
Add support for rewriting the
MATCH
clause after parsing. (#580)
π Bug Fixes
-
Add support for label expressions. (#582)
-
Correctly track identifiable elements. (#579)
2023.0.1
This patch releases adds a build-in length
function for paths (thanks @Lukasmp3 for the request) and fixes issues when running Cypher-DSL on the module path (see c7747ca35 on main for more information).
2023.0.0
Welcome to 2023, welcome Java 17, welcome Neo4j 5 clauses. This is the first release of the Cypher-DSL requiring Java 17. This is in line with Neo4j itself, Spring Data Neo4j 7 and several other frameworks out there. This allows for more concise code (which is nice for us) as well as using the Neo4j 5 parser in neo4j-cypher-dsl-parser
module. Bumping the JDK warrants a major upgrade already.
Apart from that we have been very reluctant on breaking changes. As a matter of fact, close to none has been necessary. One of the few improvements that might need changes on your side is #551 (Commit 10080df) in which we improved the WITH
clause: You might see ambiguous method errors and the fix can be seen here for example: Either use JDK 17 reserved name var
for local variable type-inference or use explicit IdentifiableElement
.
There’s a lot of new stuff as well: You can now use Expressions.count
to build new Neo4j 5 COUNT
expressions and we do support the USE
clause for composite database queries as well.
Please fear not if you are still on JDK 8: We will maintain the 2022.8.x branch at least as long as Spring Data Neo4j 6.3 is maintained, as the latter is build on top of the Cypher-DSL and is JDK 8, too.
Thanks a lot to our friend @ikwattro from @graphaware for his continuous and well appreciated feedback and input to this project.
π Features
-
Add support for the
COUNT {}
sub-query expressions. (#546) -
Pretty print
USE
clause proper. (#543, thanks to @ikwattro for contributing this) -
Add support for the
USE
clause in the DSL. (#542)
ποΈ Refactorings
-
Improve
returning
andwith
. (#551) -
Allow
yield *
for standalone calls with arguments, too. (#545, thanks to @zakjan taking the time and report this) -
Upgrade the parser module to use the new Neo4j 5 parser. (#503)
-
Migrate the project to Java 17. (#518)
-
Prevent usage of
REMOVE
item insideSET
clause (during RT). (#506)
π Documentation
-
Update changelog.
-
Add section about dialect support.
-
Make clear that pretty printing does not always escape names.
-
Document correct Java version in
README.adoc
.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump assertj-core from 3.23.1 to 3.24.1 (#549)
-
Bump checker-qual from 3.28.0 to 3.29.0 (#548)
-
Bump neo4j-java-driver from 5.3.0 to 5.3.1 (#535)
-
Bump spring-boot-starter-parent from 3.0.0 to 3.0.1 (#534)
-
Bump checkstyle from 10.5.0 to 10.6.0 (#537)
-
Bump mockito.version from 4.10.0 to 4.11.0 (#536)
-
Bump neo4j-cypher-javacc-parser from 5.2.0 to 5.3.0 (#529)
-
Bump annotations from 23.0.0 to 23.1.0 (#521)
-
Bump compile-testing from 0.20 to 0.21.0 (#526)
-
Bump reactor-bom from 2022.0.0 to 2022.0.1 (#527)
-
Bump mockito.version from 4.9.0 to 4.10.0 (#528)
-
Bump spring-boot-starter-parent from 2.7.5 to 3.0.0 (#509)
-
Bump neo4j-java-driver from 4.4.9 to 5.3.0 (#508)
-
Bump checker-qual from 3.27.0 to 3.28.0 (#517)
-
Bump compile-testing from 0.19 to 0.20 (#516)
-
Bump native-maven-plugin from 0.9.18 to 0.9.19 (#515)
-
Bump joda-time from 2.12.1 to 2.12.2 (#514)
-
Bump jackson-bom from 2.14.0 to 2.14.1 (#513)
-
Bump archunit from 1.0.0 to 1.0.1 (#512)
-
Bump native-maven-plugin from 0.9.17 to 0.9.18 (#511)
-
Bump checkstyle from 10.4 to 10.5.0 (#510)
-
π Build
-
Add more tests for GH-547.
-
Define JaCoCo config in plugin-management. (#541)
-
Add
license-maven-plugin
for checking Apache 2 compatible license and header formatting. -
Fix quality gate.
-
Verify examples on Java LTS and next version.
-
Fix docs build.
-
Upgrade various actions to non-deprecated versions. (#519)
2022.10
2022.9
2022.9.2
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump spring-boot-starter-parent from 2.7.5 to 2.7.18
-
Update Spring Data Neo4j from 6.3.14 to 6.3.18
-
Bump reactor-bom from 2022.0.2 to 2022.0.15
-
Update Neo4j from 4.4.23 to 4.4.29
-
Update Neo4j Java Driver from 4.4.12 to 4.4.13
-
Update Neo4j Java Driver from 4.4.13 to 4.4.15
-
Bump testcontainers.version from 1.17.6 to 1.19.7
-
2022.8
2022.8.4
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump checker-qual from 3.29.0 to 3.30.0 (#601)
-
Bump maven-enforcer-plugin from 3.1.0 to 3.2.1 (#600)
-
Bump checkstyle from 10.6.0 to 10.7.0 (#598)
-
Bump asciidoctorj-diagram from 2.2.3 to 2.2.4 (#597)
-
Bump jackson-bom from 2.14.1 to 2.14.2 (#594)
-
Bump assertj-core from 3.24.1 to 3.24.2 (#576)
-
Bump maven-checkstyle-plugin from 3.2.0 to 3.2.1 (#564)
-
Bump junit-bom from 5.9.1 to 5.9.2 (#563)
-
Bump reactor-bom from 2022.0.1 to 2022.0.2 (#559)
-
Bump mockito.version from 4.11.0 to 5.0.0 (#558)
-
Bump annotations from 23.1.0 to 24.0.0 (#557)
-
Bump jna from 5.12.1 to 5.13.0 (#556)
-
2022.8.3
2022.8.2
Thanks to @ikwattro from @graphaware for investing his time and creating valuable tickets for this release.
2022.8.1
ποΈ Refactorings
-
Apply learnings from full JDK 17 migrations.
-
Prevent usage of
REMOVE
item insideSET
clause (during RT). (#506)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump checker-qual from 3.27.0 to 3.28.0 (#517)
-
Bump compile-testing from 0.19 to 0.20 (#516)
-
Bump native-maven-plugin from 0.9.18 to 0.9.19 (#515)
-
Bump joda-time from 2.12.1 to 2.12.2 (#514)
-
Bump jackson-bom from 2.14.0 to 2.14.1 (#513)
-
Bump archunit from 1.0.0 to 1.0.1 (#512)
-
Bump native-maven-plugin from 0.9.17 to 0.9.18 (#511)
-
Bump checkstyle from 10.4 to 10.5.0 (#510)
-
2022.8.0
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump testcontainers.version from 1.17.5 to 1.17.6 (#502)
-
Bump maven-install-plugin from 3.0.1 to 3.1.0 (#501)
-
Bump japicmp-maven-plugin from 0.16.0 to 0.17.1 (#499)
-
Bump mockito.version from 4.8.1 to 4.9.0 (#498)
-
Bump jackson-bom from 2.13.4.20221013 to 2.14.0 (#492)
-
Bump checker-qual from 3.26.0 to 3.27.0 (#493)
-
Bump reactor-bom from 2020.0.24 to 2022.0.0 (#495)
-
Bump native-maven-plugin from 0.9.16 to 0.9.17 (#491)
-
Bump maven-shade-plugin from 3.4.0 to 3.4.1 (#487)
-
Bump checkstyle from 10.3.4 to 10.4 (#488)
-
Bump joda-time from 2.12.0 to 2.12.1 (#486)
-
Bump spring-boot-starter-parent from 2.7.4 to 2.7.5 (#485)
-
Bump asciidoctorj from 2.5.6 to 2.5.7 (#483)
-
Bump native-maven-plugin from 0.9.14 to 0.9.16 (#482)
-
Bump mockito.version from 4.8.0 to 4.8.1 (#481)
-
2022.7
2022.7.3
π Features
-
Add
point.withinBBox
and convenience methods for cartesian points and coordinates. (#475)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump jackson-bom from 2.13.4 to 2.13.4.20221013 (#479)
-
Bump neo4j-cypher-javacc-parser from 4.4.11 to 4.4.12 (#478)
-
Bump reactor-bom from 2020.0.23 to 2020.0.24 (#477)
-
Bump joda-time from 2.11.2 to 2.12.0 (#476)
-
Bump archunit from 0.23.1 to 1.0.0 (#471)
-
Bump neo4j-java-driver from 4.4.6 to 4.4.9 (#474)
-
Bump testcontainers.version from 1.17.3 to 1.17.5 (#470)
-
Bump checker-qual from 3.25.0 to 3.26.0 (#472)
-
Bump asm from 9.3 to 9.4 (#468)
-
Bump joda-time from 2.11.1 to 2.11.2 (#465)
-
Bump spring-boot-starter-parent from 2.7.3 to 2.7.4 (#464)
-
Bump junit-bom from 5.9.0 to 5.9.1 (#463)
-
Bump asciidoctorj from 2.5.5 to 2.5.6 (#462)
-
Bump checkstyle from 10.3.3 to 10.3.4 (#461)
-
Bump native-maven-plugin from 0.9.13 to 0.9.14 (#460)
-
Bump spring-data-neo4j from 6.3.2 to 6.3.3 (#459)
-
Bump jqassistant.plugin.git from 1.8.0 to 1.9.0 (#458)
-
Bump maven-jar-plugin from 3.2.2 to 3.3.0 (#457)
-
Bump reactor-bom from 2020.0.22 to 2020.0.23 (#456)
-
Bump maven-shade-plugin from 3.3.0 to 3.4.0 (#455)
-
Bump maven-pmd-plugin from 3.18.0 to 3.19.0 (#454)
-
Bump neo4j-cypher-javacc-parser from 4.4.10 to 4.4.11 (#452)
-
Bump mockito.version from 4.7.0 to 4.8.0 (#451)
-
2022.7.2
No breaking changes. This adds a new module - neo4j-cypher-dsl-schema-name-support
- that contains only one class, dedicated to sanitise and quote names that are meant to be used as labels and types. We are using it internally for all our quoting needs and if you have the need in your application to create dynamic queries that deal with the modification of labels and types, you might want to have a look at that module. It is dependency free and safe to shade. The background to do label and type manipulation is this: Cypher does not support them as parameters, you need to concatenate your query for this. In all other cases, please use proper parameter, but especially for string values.
Thanks to @AzuObs @AlexNeo4J and @robsdedude for their feedback on this work and also to @harshitp-fens for their inspiration of the ON_DELETE_ITEM
parser callback.
π Features
-
Provide
ON_DELETE_ITEM
event type. (#449) -
Introduce standalone schema-name support module. (#445)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump checker-qual from 3.24.0 to 3.25.0 (#448)
-
Bump japicmp-maven-plugin from 0.15.7 to 0.16.0 (#447)
-
Bump jackson-bom from 2.13.3 to 2.13.4 (#446)
-
Bump checkstyle from 10.3.2 to 10.3.3 (#444)
-
Bump maven-checkstyle-plugin from 3.1.2 to 3.2.0 (#443)
-
Bump maven-pmd-plugin from 3.17.0 to 3.18.0 (#442)
-
Bump joda-time from 2.11.0 to 2.11.1 (#441)
-
2022.7.1
No breaking changes. This is an important bug-fix release and a safe drop-in replacement for 2022.7.0 and all 2022.6 releases. If you can life with some deprecation warnings, it can be used as a drop-in for the 2022.5 and 2022.4 series, too.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump mockito.version from 4.6.1 to 4.7.0 (#434):
-
Bump reactor-bom from 2020.0.21 to 2020.0.22 (#433):
-
Bump joda-time from 2.10.14 to 2.11.0 (#432):
-
Bump neo4j-cypher-javacc-parser from 4.4.9 to 4.4.10 (#431):
-
Bump maven-javadoc-plugin from 3.4.0 to 3.4.1 (#430):
-
Bump spring-boot-starter-parent from 2.7.2 to 2.7.3 (#439)
-
Bump flatten-maven-plugin from 1.2.7 to 1.3.0 (#437):
-
2022.7.0
No breaking changes, the minor version has been bumped due to new default methods of internal interfaces. This release is - again - a safe drop-in replacement for the prior (2022.6.1) one.
Thanks to @AakashSorathiya and Nicolas Mervaillie for their input on this release.
π Features
-
Add support for
includesAll
andincludesAny
operations on expressions for list properties -
Support
org.neo4j.cypher.internal.ast.factory.ASTExpressionFactory#ands
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump maven-site-plugin from 3.12.0 to 3.12.1 (#428)
-
Bump checker-qual from 3.23.0 to 3.24.0 (#429)
-
Bump checkstyle from 10.3.1 to 10.3.2 (#425)
-
Bump junit-bom from 5.8.2 to 5.9.0 (#424)
-
Bump maven-resources-plugin from 3.2.0 to 3.3.0 (#423)
-
Bump asciidoctorj from 2.5.4 to 2.5.5 (#422)
-
2022.6
2022.6.1
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump neo4j-cypher-javacc-parser from 4.4.8 to 4.4.9 (#418)
-
Bump maven-install-plugin from 3.0.0-M1 to 3.0.1 (#417)
-
Bump spring-boot-starter-parent from 2.7.1 to 2.7.2 (#416)
-
Bump maven-deploy-plugin from 3.0.0-M2 to 3.0.0 (#415)
-
Bump exec-maven-plugin from 3.0.0 to 3.1.0 (#414)
-
Bump native-maven-plugin from 0.9.12 to 0.9.13 (#413)
-
Bump spring-data-neo4j from 6.3.1 to 6.3.2 (#412)
-
Bump reactor-bom from 2020.0.20 to 2020.0.21 (#411)
-
Bump checker-qual from 3.22.2 to 3.23.0 (#410)
-
2022.6.0
Functions.internalId()
has been deprecated to accomodate for Neo4j 5 later this year.
We are unsure if we will do this to id()
, too (The method with the same name will be deprecated in Neo4j 5 and eventually
be replaced by elementId()
).
2022.5
2022.5.0
No breaking changes, the minor version has been bumped due to new default methods of internal interfaces. This release is - again - a safe drop-in replacement for the prior (2022.4.0) one.
Thanks to @hindog, @bhspencer, @Hardu2203 and @irene221b for their input on this release.
π Features
-
Add explicit
set
operation toPropertyContainer
. (#394) -
Support "WITH *, <expr>" by handling the 'returnAll' flag received from parser (#367)
ποΈ Refactorings
-
refactor: Remove superfluous whitespaces before
MapExpression
in pretty printer. (#401)
π Documentation
-
Add an example how to use Cypher parameters with`CypherdslStatementExecutor`. (#395)
-
Improve JavaDoc of
TemporalLiteral
. -
Add correction method description.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump jna from 5.11.0 to 5.12.0 (#399)
-
Bump spring-boot-starter-parent from 2.7.0 to 2.7.1 (#398)
-
Bump spring-data-neo4j from 6.3.0 to 6.3.1 (#397)
-
Bump native-maven-plugin from 0.9.11 to 0.9.12 (#396)
-
Bump reactor-bom from 2020.0.19 to 2020.0.20 (#392)
-
Bump checker-qual from 3.22.1 to 3.22.2 (#390)
-
Bump neo4j-cypher-javacc-parser from 4.4.7 to 4.4.8 (#391)
-
Bump maven-enforcer-plugin from 3.0.0 to 3.1.0 (#386)
-
Bump joda-time from 2.10.10 to 2.10.14 (#387)
-
Bump asciidoctorj from 2.5.3 to 2.5.4 (#380)
-
Bump assertj-core from 3.22.0 to 3.23.1 (#383)
-
Bump checker-qual from 3.22.0 to 3.22.1 (#382)
-
Bump mockito.version from 4.6.0 to 4.6.1 (#381)
-
Bump neo4j-java-driver from 4.4.5 to 4.4.6 (#379)
-
Bump maven-pmd-plugin from 3.16.0 to 3.17.0 (#378)
-
Bump asciidoctorj-diagram from 2.2.1 to 2.2.3 (#377)
-
Bump mockito.version from 4.5.1 to 4.6.0 (#376)
-
Bump checkstyle from 10.2 to 10.3 (#375)
-
Bump neo4j-cypher-javacc-parser from 4.4.6 to 4.4.7 (#373)
-
Bump testcontainers.version from 1.17.1 to 1.17.2 (#371)
-
Bump spring-data-neo4j from 6.2.4 to 6.3.0 (#368)
-
Bump jackson-bom from 2.13.2.20220328 to 2.13.3 (#370)
-
Bump reactor-bom from 2020.0.18 to 2020.0.19 (#369)
-
2022.4
2022.4.0
-
Added
withoutResults
to both in-statement and standalone call-builders so that one can use procedures without results inside a pipeline. This won’t break anything, as the corresponding interface is not meant to implemented by downstream libraries -
Compound conditions are now correctly immutable (as stated by the contract and its JavaDoc). This might break things if you have them changed inflight.
Thanks to @Andy2003 for his input on this release.
π Bug Fixes
-
Make
CompoundCondition
immutable obliging the interfaces contract. (#365) -
Don’t skip symbolic names if present and already in scope. (#363)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump testcontainers.version from 1.16.3 to 1.17.1 (#352)
-
Bump reactor-bom from 2020.0.17 to 2020.0.18 (#353)
-
Bump mockito.version from 4.4.0 to 4.5.1 (#354)
-
Bump checkstyle from 10.1 to 10.2 (#355)
-
Bump spring-boot-starter-parent from 2.6.6 to 2.6.7 (#356)
-
Bump maven-javadoc-plugin from 3.3.2 to 3.4.0 (#357)
-
Bump maven-site-plugin from 3.11.0 to 3.12.0 (#358)
-
Bump spring-data-neo4j from 6.2.3 to 6.2.4 (#359)
-
Bump neo4j-cypher-javacc-parser from 4.4.5 to 4.4.6 (#360)
-
Bump checker-qual from 3.21.4 to 3.22.0 (#364)
-
2022.3
2022.3.0
No breaking changes. The minor version has been incremented due to the following changes:
-
Changes in the
ExposesSubqueryCall
(new methods to exposecallInTransactions
, but that interface is not meant for external implementations anyway) -
Added a new type
Dialect
and a new default methodenterWithResult
on theVisitor
interface (have a look at the JavaDoc for the rationale behind it).
π Features
-
Add support for dialects.
-
Add support for toString(Expression). (#344)
-
Support
CALL {} IN TRANSACTIONS
. -
Add parameter callbacks to the parser. (#336)
π Bug Fixes
-
Prevent
ClassCastException
when usingString
arguments to import variables into a subquery. -
Make generated static model usable with self referential associations. (#337, Thanks to @ChristophB for his input on #335).
-
Fix tag of CypherParser entry point. (docs)
π Documentation
-
Add information about GraalVM 21.3.0 and
org.graalvm.buildtools:native-maven-plugin
to CONTRIBUTING.adoc.
π Build
-
Fix publish_docs workflow.
-
Add support for registering
allDeclaredConstructors
. (#342) -
Add
RegisterForReflection
and processor replacing static reflection-config.json. (#341)
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump jackson-bom from 2.13.2 to 2.13.2.20220328 (#346)
-
Bump asm from 9.2 to 9.3 (#347)
-
Bump jacoco-maven-plugin from 0.8.7 to 0.8.8 (#345)
-
Update managed version of error_prone_annotations to 2.12.1, avoiding compilation issues in IDEA.
-
Bump spring-boot-starter-parent from 2.6.5 to 2.6.6 (#340)
-
Bump checker-qual from 3.21.3 to 3.21.4 (#339)
-
Bump maven-shade-plugin from 3.2.4 to 3.3.0 (#338)
-
2022.2
2022.2.1
No breaking changes.
π Features
-
Add randomUUID to predefined functions.
-
Support additional mutate expression types. (#312)
π Bug Fixes
-
Don’t create empty
WITH
clause without renames. (#320) -
Fix rendering of nested FOREACH statements. (#318)
-
Check for field type too when computing internalId usage.
π§Ή Housekeeping
-
Remove Awaitility test-dependency.
-
Dependency upgrades:
-
Bump spring-data-neo4j from 6.2.2 to 6.2.3 (#332)
-
Bump neo4j-cypher-javacc-parser from 4.4.4 to 4.4.5 (#330)
-
Bump checkstyle from 10.0 to 10.1 (#329)
-
Bump jna from 5.10.0 to 5.11.0 (#331)
-
Bump spring-boot-starter-parent from 2.6.4 to 2.6.5 (#333)
-
Bump native-maven-plugin from 0.9.10 to 0.9.11 (#334)
-
Bump neo4j-java-driver from 4.4.3 to 4.4.5 (#328)
-
Bump reactor-bom from 2020.0.16 to 2020.0.17 (#327)
-
Bump mockito.version from 4.3.1 to 4.4.0 (#325)
-
Bump checkstyle from 9.3 to 10.0 (#323)
-
Bump guava from 31.0.1-jre to 31.1-jre (#324)
-
Bump checker-qual from 3.21.2 to 3.21.3 (#322)
-
Bump awaitility from 4.1.1 to 4.2.0 (#321)
-
Bump japicmp-maven-plugin from 0.15.6 to 0.15.7 (#313)
-
Bump spring-boot-starter-parent from 2.6.3 to 2.6.4 (#314)
-
2022.2.0
No breaking changes. The minor version has been incremented to notify about a couple of new methods in the parser module, allowing for more and different types of parsing events to be emitted.
Thanks to @ikwattro for his input and feedback in this release.
π Features
-
Emit pattern created event on merge clauses.
-
Add callbacks for a "pattern element created event". (#303)
π Documentation
-
Add an example how to track changed properties to nodes.
-
Add rewrite example.
-
Add examples how to extract modified labels for the Cypher parser.
π Build
-
Fix surefire settings.
-
Add a 'fast' profile.
-
Reorder module-info.java creation before shading so that javadoc wont fail on vanilla JDK.
π§Ή Housekeeping
-
Dependency upgrades:
-
Bump maven-site-plugin from 3.10.0 to 3.11.0 (#311)
-
Bump native-maven-plugin from 0.9.9 to 0.9.10 (#310)
-
Bump maven-pmd-plugin from 3.15.0 to 3.16.0 (#309)
-
Bump spring-data-neo4j from 6.2.1 to 6.2.2 (#308)
-
Bump reactor-bom from 2020.0.15 to 2020.0.16 (#307)
-
Bump slf4j.version from 1.7.35 to 1.7.36 (#306)
-
Bump maven-javadoc-plugin from 3.3.1 to 3.3.2 (#305)
-
Bump neo4j-cypher-javacc-parser from 4.4.3 to 4.4.4 (#304)
-
Bump checker-qual from 3.21.1 to 3.21.2 (#298)
-
2022.1
2022.1.0
No breaking changes. The minor version has been incremented to notify about new default methods in our interfaces. Those shouldn’t concern you as a user though, as they are not meant to be implemented by you.
Noteworthy
Our integration tests on GitHub now uses the official GraalVM action: https://github.com/marketplace/actions/github-action-for-graalvm. Thanks, Gerrit, for integrating it.
π§Ή Housekeeping
-
Some polishing (mainly working on getting a "warning free" build in all the tools)
-
Tons of dependency upgrades:
-
Bump testcontainers.version from 1.16.2 to 1.16.3 (#289)
-
Bump spring-boot-starter-parent from 2.6.2 to 2.6.3 (#290)
-
Bump mockito.version from 4.2.0 to 4.3.1 (#291)
-
Bump slf4j.version from 1.7.33 to 1.7.35 (#292)
-
Bump japicmp-maven-plugin from 0.15.4 to 0.15.6 (#293)
-
Bump neo4j-java-driver from 4.4.2 to 4.4.3 (#294)
-
Bump checkstyle from 9.2.1 to 9.3 (#295)
-
Bump asciidoctor-maven-plugin from 2.2.1 to 2.2.2 (#296)
-
Bump asciidoctorj from 2.5.2 to 2.5.3 (#285)
-
Bump maven-jar-plugin from 3.2.1 to 3.2.2 (#284)
-
Bump spring-data-neo4j from 6.2.0 to 6.2.1 (#283)
-
Bump reactor-bom from 2020.0.14 to 2020.0.15 (#282)
-
Bump slf4j.version from 1.7.32 to 1.7.33 (#281)
-
Bump neo4j-cypher-javacc-parser from 4.4.2 to 4.4.3 (#280)
-
Bump maven-jar-plugin from 3.2.0 to 3.2.1 (#277)
-
Bump checker-qual from 3.21.0 to 3.21.1 (#276)
-
Bump assertj-core from 3.21.0 to 3.22.0 (#272)
-
Bump maven-site-plugin from 3.9.1 to 3.10.0 (#270)
-
Bump maven-deploy-plugin from 3.0.0-M1 to 3.0.0-M2 (#271)
-
Bump checkstyle from 9.2 to 9.2.1 (#269)
-
Bump spring-boot-starter-parent from 2.6.1 to 2.6.2 (#268)
-
Bump Maven to 3.8.4.
-
2022.0
2022.0.0
Starting with the 2022 release line, all current experimental warnings have been removed, and we consider our API stable.
Noteworthy
As we have marked the API as stable we do enforce semantic versioning in our builds now.
The parser module neo4j-cypher-dsl-parser
has been updated to the Neo4j 4.4 parser and therefore doesn’t bring in Scala dependencies anymore.
And last but not least, we added the Contributor Covenant Code of Conduct.
π Bug Fixes
-
Fix broken asciidoc includes.
-
Give messages constant a better name (The bundle name we used might clash with other bundle names).
π§Ή Housekeeping
-
Tons of dependency upgrades:
-
Bump reactor-bom from 2020.0.13 to 2020.0.14 (#265)
-
Bump checker-qual from 3.20.0 to 3.21.0 (#264)
-
Bump mockito.version from 4.1.0 to 4.2.0 (#263)
-
Bump neo4j-cypher-javacc-parser from 4.4.0 to 4.4.2 (#262)
-
Bump checker-qual from 3.19.0 to 3.20.0 (#261)
-
Bump neo4j-java-driver from 4.4.1 to 4.4.2 (#260)
-
Bump spring-boot-starter-parent from 2.5.6 to 2.6.1 (#259)
-
Bump checkstyle from 9.1 to 9.2 (#256)
-
Bump junit-bom from 5.8.1 to 5.8.2 (#257)
-
Bump mockito.version from 4.0.0 to 4.1.0 (#255)
-
Thanks again to Andy for his contributions and feedback.
2021.4
2021.4.2
2021.4.0
2021.4.0 updates the optional dependency to Querydsl to 5.0.0. While this is API not a breaking change, it can be when the Cypher-DSL is run together with Querydsl on the Java Module path. Querydsl maintainer finally introduced automatic module names for all their module on which we can no reliable depend. As that module name is however different from the generated one, it will be a breaking change on the module path. Therefore we bump our version, too.
2021.3
2021.3.3
2021.3.3 is a pure housekeeping release, however a release we are proud of. We do analyze this project now with SonarQube vie Sonarcloud and are happy to announce that we have a quadruple A rating:
In addition, we finally invited Dependabot taking care of at least creating the PRs.
2021.3.2
π Features
-
Add support for QueryDSL’s
equalsIgnoreCase
operator -
GH-204 - Provide access to identifiable expressions (See Section 2.2.2.3)
2021.3.1
2021.3.1 is a pure bug fix release. API Guardian cannot be an optional dependency, otherwise compiling programs with -Werror
will fail, as the @API
-annotation has runtime and not class retention.
2021.3.0
π New module: The Cypher-DSL-Parser
2021.3 builds straight upon 2021.2.3, with few additions to the existing API, that didn’t quite fit with a patch release,
but belong conceptually into this release, which brings a completely new module: The neo4j-cypher-dsl-parser
.
What’s behind that name? A Cypher-Parser build on the official Neo4j 4.3 parser frontend and creating a Cypher-DSL-AST or single expressions usable in the context of the Cypher-DSL.
The module lives under the following coordinates org.neo4j:neo4j-cypher-dsl-parser
and requires JDK 11+ (the same version like Neo4j does).
We created a couple of examples, but we are sure you will have tons of more ideas and therefore
a looking for your feedback.
Here’s a sneak preview. It shows you can add a user supplied Cypher fragment to something you are building using the DSL.
var userProvidedCypher
= "MATCH (this)-[:LINK]-(o:Other) RETURN o as result";
var userStatement = CypherParser.parse(userProvidedCypher);
var node = Cypher.node("Node").named("node");
var result = Cypher.name("result");
var cypher = Cypher
.match(node)
.call(
userStatement,
node.as("this")
)
.returning(result.project("foo", "bar"))
.build()
.getCypher();
For this release a big thank you goes out to the Cypher-operations team at Neo4j, listening to our requests and ideas!
2021.2
2021.2.3
2021.2.3 is a rather big release as it contains many small improvements and API functionality required by our next major release. Those are brought in now so that they can be benefial to others without bumping a major version.
π Features
-
GH-195 - Add collection parameter support for
ExposesReturning
. -
Introduce a
ExposesPatternLengthAccessors
for uniform access to relationships and chains thereof. [improvement] -
Allow creating instances of
FunctionInvocation
directly. [improvement] -
Provide factory methods of
MapProjection
andKeyValueMapEntry
as public API. -
Provide
set
andremove
labels operation as public API. -
Provide
set
andmutate
of expressions as public API. -
Provide factory methods of
Hints
as public API. -
GH-200 - Provide an API to define named paths based on a
Node
pattern. -
Provide an option to escape names only when necessary. [improvement]
π Documentation
-
Add documentation for escaping names.
-
GH-198 - Fix spelling errors in JavaDoc and documentation.
π Bug Fixes
-
Make
Case
an interface and let it extendExpression
. [bug] -
GH-197 - Fix eagerly resolved symbolic names in negated pattern conditions.
-
GH-197 - Clear name cache after leaving a statement.
-
GH-199 - Bring back
with(Named⦠vars)
.
π§Ή Housekeeping
-
Don’t use fixed driver versions in doc.
-
Pass builder as constructor argument.
-
Improve
Return
andWith
internals. -
Update Driver, SDN integration and Spring Boot example dependencies.
-
GH-202 - Make API Guardian an optional / provided dependency.
Thanks to @meistermeier and @aldrinm for their contributions.
2021.2.2
π Features
-
Allow all expresions to be used as conditions. [improvement]
-
Add support for unary minus and plus operations. [new-feature]
-
Add support for generatic dynamic distinct aggregating function calls. [new-feature]
-
GH-190 - Introduce a union type for named things and aliased expressions.
-
Provide means to pass additional types to the relationship base class. [new-feature]
-
GH-193 - Allow MATCH after YIELD.
-
GH-189 - Provide an alternate api for methods consuming collections via vargs.
π Bug Fixes
-
Fix parameter collector when running as GraalVM native image
-
GH-192 - Don’t introduce new symbolic names in conditional pattern expressions.
π§Ή Housekeeping
-
GH-178 - Upgrade SDN 6 examples to Spring Boot 2.5 final.
Thanks to @meistermeier for the contribution of the API improvements in regard to collections.
2021.2.1
π Features
-
Distinguish between statements and result statements: The Cypher-DSL knows whether a statement would actually return data or not
-
Provide optional integration with the Neo4j-Java-Driver to execute statements.
-
Allow to register Spring converters with the annotation processor. [codegen]
-
GH-182 - Add support for scalar converter functions.
-
GH-183 - Add trim function.
-
GH-184 - Add split function.
-
GH-180 - Add support for LOAD CSV and friends.
-
GH-187 - Add
returningRaw
for returning arbitrary (aliased) Cypher fragments (bot as part of a statement or as a generalRETURN xxx
clause without preceding query) -
Resolve named parameters in raw literals: You can mix now the expression placeholder
$E
and named parameters in raw Cypher literals giving you much more flexibility in regards what to pass to the raw litera.
π Bug Fixes
-
GH-177 - Create a valid loadable and instantiable name when working on nested, inner classes. [codegen]
-
GH-186 - Pretty print subqueries and fix double rendering of Labels after subquery.
π§Ή Housekeeping
-
Remove unnecessary subpackage 'valid'. [codegen] (test code only)
-
Upgrade to GraalVM 21.1.0.
-
Update Spring dependencies for codegen.
Thanks to @Andy2003 for contributing to this release.
2021.2.0
2021.2 doesn’t bring any new features apart from being now a Java library supporting the Java module system not only with
automatic module names but also with a correct module-info.java
when running on JDK 11+ on the module path.
The Cypher-DSL uses the technique of JEP 238: Multi-Release JAR Files to provide a
module-info.java
for projects being on JDK 11+.
The MR-Jar allows us to compile for JDK 8 but also support JDK 11 (we choose 11 as it is the current LTS release as time of writing).
To use the Cypher-DSL in a modular application you would need to require the following modules:
module org.neo4j.cypherdsl.examples.core {
requires org.neo4j.cypherdsl.core;
}
This release comes with a small catch: We do support using some QueryDSL constructs. Query-DSL will have correct automatic module names in their 5.x release and we asked them to backport those to the 4.x line on which the Cypher-DSL optionally depends (See 2805).
Until then we statically require (that is "optional" in module speak) Query-DSL via the artifact name.
This can cause errors when the artifact (querydsl-core.jar
) is renamed via the build process or similar.
We are gonna improve that as soon as we can depend on fixed automatic module names.
Apart from this big change there is no change in any public API. This release should be a drop-in replacement for the prior release.
A big thank you to @sormuras for his invaluable lessons about the Java module system.
2021.1
2021.1.2
This release comes with two notable things: It uses a couple of annotations on the API to guide developers using it correctly. IDEs like IDEA will now issue warnings if you don’t use a returned builder, or a new instance of an object while wrongly assuming you mutated state.
In the light of that we discovered that the RelationshipChain
pattern was mutable and returning a mutated instance while
it should have returned a fresh one.
Warning This might be a breaking change for users having code like this:
var pattern = Cypher.node("Start").named("s")
.relationshipTo(Cypher.anyNode())
.relationshipTo(Cypher.node("End").named("e"));
pattern.named("x");
Prior to 2021.1.2 this would give the pattern the name x
and modify it in place.
From 2021.1.2 onwards you must use the returned value:
pattern = pattern.named("x");
We think that this change is crucial and necessary as all other patterns are immutable as intended and in sum, they build up truly immutable statements. One pattern that is mutable like the above invalides the whole guarantee about the statement.
π Features
-
Add
named(SymbolicName s)
to RelationshipChain. -
Generate $TYPE field containing the relationship type. [SDN 6 Annotation Processor]
-
Introduce some optional annotations for guidance along the api.
2021.1.1
This is a drop-in replacement for 2021.1.0. Introducing the interface for Property
broke the mutate
operation,
for which no test was in place. This and the bug has been fixed.
2021.1.0
2021.1.0 comes with a ton of new features and a handful of breaking changes.
Fear not, the breaking changes are resolvable by recompiling your application.
We turned Node
, Relationship
and Property
into interfaces and provide now NodeBase
and RelationshipBase
so that you can
use them to build a static meta-model of your application. A PropertyBase
might follow.
Find out everything about the new possibility to define a static meta model in the manual.
The manual also includes a major part about the two new modules we offer:
org.neo4j:neo4j-cypher-dsl-codegen-core
and org.neo4j:neo4j-cypher-dsl-codegen-sdn6
.
neo4j-cypher-dsl-codegen-core
provides the infrastructure necessary to build code generators for creating a domain model
following our recommendation and neo4j-cypher-dsl-codegen-sdn6
is a first implementation of that:
A Java annotation processor that can be added to any Spring Data Neo4j 6 project in version 6.0.6 or higher.
It will find your annotated domain classes and turn them into a model you can use to build queries.
Last but not least: We added support for some expressions of the more generic QueryDSL.
This will require com.querydsl:querydsl-core
on the class path but only if you decide to call Cypher#adapt(foreignExpression)
.
This is a feature that is driven by Spring Data Neo4j 6.1 in which we build upon this to provide a QuerydslPredicateExecutor
.
Find more in this section of the manual.
π Features
-
GH-154 - Make Node and Relationship extendable.
-
GH-155 - Provide infrastructure for generating a static meta model.
-
GH-156 - Create an annotation processor for Spring Data Neo4j 6.
-
GH-167 - Add support for some Query-DSL expressions.
-
Introduce a statement context for allowing anonymous parameters (use
Cypher#anonParameter()
to define a parameter with a value but without a name. The name will be accessible on the statement after rendering). -
Make rendering of constants as parameters configurable.
-
Allow specification of the direction while creating a sort item.
-
Introduce an interface for Property.
2021.0
2021.0.2
This will already be the last release of the 2021.0 line. 2021.1 will be API compatible but not ABI compatible, as some classes have been changed into interfaces. That means it is not a drop in replacement, but your application needs to be recompiled. |
π Features
-
GH-157 - Provide a method to turn a Java map into an expression.
-
GH-158 - Improve pretty printing of subqueries.
-
Allow the use of raw cypher as expressions.
-
Allow symbolic names to be used as aliases.
-
Cache some symbolic names.
-
Add support for the keys() function.
π Bug Fixes
-
GH-149 - Avoid possible stackoverflow exception during visitor traversal.
-
GH-159 - Fix missing labels for nodes after
WITH
.
π§Ή Housekeeping
-
GH-148 - Add jQAssistant rules and improve building documentation.
-
Add Maven PMD plugin.
Thanks Andy for the improvements of the pretty printer.
2021.0.1
π Features
-
GH-147 - Configuration infrastructure for renderer. First use case being a simple, pretty printing renderer.
The feature looks like this:
var c = node("Configuration").named("c");
var d = node("Cypher-DSL").named("d");
var mergeStatement = merge(c.relationshipTo(d, "CONFIGURES"))
.onCreate()
.set(
d.property("version").to(literalOf("2021.0.1")),
c.property("prettyPrint").to(literalTrue())
)
.onMatch().set(c.property("indentStyle").to(literalOf("TAB")))
.returning(d).build();
var renderer = Renderer.getRenderer(Configuration.prettyPrinting());
System.out.println(renderer.render(mergeStatement));
and gives you:
MERGE (c:Configuration)-[:CONFIGURES]->(d:`Cypher-DSL`)
ON CREATE SET d.version = '2021.0.1', c.prettyPrint = true
ON MATCH SET c.indentStyle = 'TAB'
RETURN d
2021.0.0
2021.0.0 comes with a lot of new features. Thanks to Andy for his contributions!
Andy is one of our first users outside Spring Data Neo4j 6. He started to use the Cypher-DSL in Neo4j GraphQL Java. Neo4j GraphQL Java is a library to translate GraphQL based schemas and queries to Cypher and execute those statements with the Neo4j database. It can be used from a wide variety of frameworks.
We are happy and proud to be part of this and even more so about the input and contribution we got back from Andy.
Of course thanks for your input in form of tickets and discussions go out to @utnaf, @aaramg, @K-Lovelace and @maximelovino as well!
Noteworthy
Two things should be mentioned:
The bugfix for GH-121 might change behavior for some users: The changes prevents the forced rendering of an alias for objects when the original object - the one that has been aliased - is passed down to the DSL after an alias has been created.
The original intention for that behaviour was related to Map projection, in which the alias is actually rendered before the object.
So now the use of an aliased expression the first time triggers a AS b
respectively b: a
in a map projection.
All further calls will just render b
. If the original object is used again, a
will be rendered. If that is not desired
in your query and you rely on the alias, make sure you use the aliased expression returned from .as("someAlias")
.
The other thing are the combined features GH-135 and GH-146.
The Statement
class has become a fully fledged accessor to the Cypher String and the parameters used and if provided,
the values for those. The following shows a small example:
var person = Cypher.node("Person").named("p");
var statement = Cypher
.match(person)
.where(person.property("nickname").isEqualTo(Cypher.parameter("nickname")))
.set(
person.property("firstName").to(Cypher.parameter("firstName").withValue("Thomas")),
person.property("name").to(Cypher.parameter("name", "Anderson"))
)
.returning(person)
.build();
assertThat(statement.getCypher())
.isEqualTo("MATCH (p:`Person`) WHERE p.nickname = $nickname SET p.firstName = $firstName, p.name = $name RETURN p");
Collection<String> parameterNames = statement.getParameterNames();
assertThat(parameterNames).containsExactlyInAnyOrder("nickname", "firstName", "name");
Map<String, Object> parameters = statement.getParameters();
assertThat(parameters).hasSize(2);
assertThat(parameters).containsEntry("firstName", "Thomas");
assertThat(parameters).containsEntry("name", "Anderson");
π Features
-
GH-122 - Add support for index hints.
-
GH-123 - Expose nested building of nested properties as public API.
-
GH-124 - Add support for Neo4j’s mathematical functions.
-
GH-127 - Allow dynamic property lookup.
-
GH-128 - Provide asConditions for RelationshipPatterns.
-
GH-129 - Allow Expressions as Parameter for Skip and Limit.
-
GH-131 - Add support for projections on symbolic names.
-
GH-133 - Allow symbolic names to be used as condition.
-
GH-135 - Collect parameters defined on a statement.
-
GH-141 - Provide a property function on all expressions.
-
GH-142 - Provide a point function accepting generic expressions as parameter.
-
GH-146 - Allow a statement to render itself.
2020.1
2020.1.2
π Features
-
GH-88 - Add support for Neo4j 4.0 subqueries.
-
GH-104 - Add support for merge actions.
-
GH-101 - Introduce asFunction on an ongoing call definition.
Further improvements:
-
Add support for EXPLAIN and PROFILE keywords.
-
Qualify a yield call (only relevant for JDK15+)
-
Fix wrong offsets in the documentation.
-
Improve JavaDoc and document internal API.
-
Allow
WITH
clause afterYIELD
. -
Improve reusability of fragments.
-
Make ORDER clause buildable.
-
Remove parts of an experimental API.
We do publish the Project info now: Project info, including the Java API.
2020.1.0
π Features
-
GH-74 - Automatically generate symbolic name if required:
Node
andRelationship
objects generate a symbolic name if required and not set -
Added several new functions
-
GH-77
properties()
-
GH-81
relationships()
-
GH-83
startNode()
,endNode()
, -
GH-89 All temporal functions
-
-
GH-76 - Added the list operator (
[]
for accessing sub lists and indexes).
π Bug Fixes
-
GH-82 - Expose all necessary interfaces for
call
-
GH-84 - Fix rendering of nested sub trees.
-
GH-95 - NPE during the creation of map projections
-
GH-96 - Make sure aliased expressions are not rendered multiple times.
π§Ή Housekeeping
-
GH-67 - Improvements in regards of Java generics.
-
GH-68 - Clean up the Functions api.
-
GH-69 - Avoid star and static imports.
-
GH-72 - Some release cleanup
-
GH-75 - Move Assert to internal utils package.
-
GH-89 -
RelationshipDetails
is now internal API. -
GH-93 - Ensure compatibility with GraalVM native.
-
GH-94 - Bring back SymbolicName#concat.
2020.0
2020.0.1
This is the first patch release for the rebooted Cypher-DSL project.
2020.0.0
This is the first version of the rebooted Neo4j Cypher-DSL project. This version has been extracted from SDN-RX.
It’s a completely revamped API and we use it in all places in SDN/RX for generating Cypher-Queries.
We use CalVer in the same way Spring does since early 2020 (see Updates to Spring Versions) from this release onwards.
You’ll find the manual of the latest release version under http://neo4j-contrib.github.io/cypher-dsl/current/ and the current development version - or main - under http://neo4j-contrib.github.io/cypher-dsl/main/.