Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ci/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ mkdir -p /tmp/jenkins-home

export JENKINS_USER=${JENKINS_USER_NAME}
export SDN_FORCE_REUSE_OF_CONTAINERS=true
export SDN_NEO4J_VERSION=5.26.2
export SDN_NEO4J_VERSION=5.26.12

MAVEN_OPTS="-Duser.name=${JENKINS_USER} -Duser.home=/tmp/jenkins-home -Dscan=false" \
./mvnw -s settings.xml -P${PROFILE} clean dependency:list verify -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-neo4j -Ddevelocity.storage.directory=/tmp/jenkins-home/.develocity-root
4 changes: 2 additions & 2 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
<blockhound.version>1.0.8.RELEASE</blockhound.version>
<checkstyle.skip>${skipTests}</checkstyle.skip>
<checkstyle.version>8.40</checkstyle.version>
<cypher-dsl.version>2024.5.1</cypher-dsl.version>
<cypher-dsl.version>2025.2.2</cypher-dsl.version>
<dist.id>spring-data-neo4j</dist.id>
<dist.key>SDNEO4J</dist.key>
<flatten-maven-plugin.version>1.7.0</flatten-maven-plugin.version>
Expand All @@ -91,7 +91,7 @@
<maven.compiler.release>${java.version}</maven.compiler.release>
<neo4j-java-driver.version>5.28.10</neo4j-java-driver.version>
<neo4j-migrations.version>2.17.3</neo4j-migrations.version>
<neo4j.version>4.4.41</neo4j.version>
<neo4j.version>5.26.12</neo4j.version>
<objenesis.version>3.0.1</objenesis.version>
<project.build.docs>${project.build.directory}/docs</project.build.docs>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,11 +52,13 @@ final class DynamicLabels implements UnaryOperator<OngoingMatchAndUpdate> {
public OngoingMatchAndUpdate apply(OngoingMatchAndUpdate ongoingMatchAndUpdate) {

OngoingMatchAndUpdate decoratedMatchAndUpdate = ongoingMatchAndUpdate;
if (!oldLabels.isEmpty()) {
decoratedMatchAndUpdate = decoratedMatchAndUpdate.remove(rootNode, oldLabels.toArray(new String[0]));
if (!this.oldLabels.isEmpty()) {
decoratedMatchAndUpdate = decoratedMatchAndUpdate.remove(this.rootNode,
Cypher.allLabels(Cypher.anonParameter(this.oldLabels)));
}
if (!newLabels.isEmpty()) {
decoratedMatchAndUpdate = decoratedMatchAndUpdate.set(rootNode, newLabels.toArray(new String[0]));
if (!this.newLabels.isEmpty()) {
decoratedMatchAndUpdate = decoratedMatchAndUpdate.set(this.rootNode,
Cypher.allLabels(Cypher.anonParameter(this.newLabels)));
}
return decoratedMatchAndUpdate;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -452,10 +452,12 @@ private <T> T saveImpl(T instance, @Nullable Collection<PropertyFilter.Projected
includedProperties, entityMetaData,
neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass())
);
Statement statement = cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer));
Optional<Entity> newOrUpdatedNode = neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer))))
.query(() -> renderer.render(statement))
.bind(entityToBeSaved)
.with(binderFunction)
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Entity.class)
.one();

Expand Down Expand Up @@ -496,11 +498,13 @@ private <T> DynamicLabels determineDynamicLabels(T entityToBeSaved, Neo4jPersist

PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved);
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
var statementReturningDynamicLabels = cypherGenerator.createStatementReturningDynamicLabels(entityMetaData);
Neo4jClient.RunnableSpec runnableQuery = neo4jClient
.query(() -> renderer.render(cypherGenerator.createStatementReturningDynamicLabels(entityMetaData)))
.query(() -> renderer.render(statementReturningDynamicLabels))
.bind(convertIdValues(idProperty, propertyAccessor.getProperty(idProperty)))
.to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels())
.to(Constants.NAME_OF_STATIC_LABELS_PARAM);
.to(Constants.NAME_OF_STATIC_LABELS_PARAM)
.bindAll(statementReturningDynamicLabels.getCatalog().getParameters());

if (entityMetaData.hasVersionProperty()) {
runnableQuery = runnableQuery
Expand Down Expand Up @@ -579,9 +583,11 @@ class Tuple3<T> {
binderFunction = TemplateSupport.createAndApplyPropertyFilter(pps, entityMetaData, binderFunction);
List<Map<String, Object>> entityList = entitiesToBeSaved.stream().map(h -> h.modifiedInstance).map(binderFunction)
.collect(Collectors.toList());
var statement = cypherGenerator.prepareSaveOfMultipleInstancesOf(entityMetaData);
Map<Value, String> idToInternalIdMapping = neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOfMultipleInstancesOf(entityMetaData)))
.query(() -> renderer.render(statement))
.bind(entityList).to(Constants.NAME_OF_ENTITY_LIST_PARAM)
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Map.Entry.class)
.mappedBy((t, r) -> new AbstractMap.SimpleEntry<>(r.get(Constants.NAME_OF_ID), TemplateSupport.convertIdOrElementIdToString(r.get(Constants.NAME_OF_ELEMENT_ID))))
.all()
Expand Down Expand Up @@ -673,7 +679,9 @@ public <T> void deleteById(Object id, Class<T> domainType) {
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
ResultSummary summary = this.neo4jClient.query(renderer.render(statement))
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), id))
.to(nameOfParameter).run();
.to(nameOfParameter)
.bindAll(statement.getCatalog().getParameters())
.run();

log.debug(() -> String.format("Deleted %d nodes and %d relationships.", summary.counters().nodesDeleted(),
summary.counters().relationshipsDeleted()));
Expand Down Expand Up @@ -724,7 +732,9 @@ public <T> void deleteAllById(Iterable<?> ids, Class<T> domainType) {
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
ResultSummary summary = this.neo4jClient.query(renderer.render(statement))
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), ids))
.to(nameOfParameter).run();
.to(nameOfParameter)
.bindAll(statement.getCatalog().getParameters())
.run();

log.debug(() -> String.format("Deleted %d nodes and %d relationships.", summary.counters().nodesDeleted(),
summary.counters().relationshipsDeleted()));
Expand Down Expand Up @@ -873,6 +883,7 @@ private <T> T processNestedRelations(
.to(Constants.FROM_ID_PARAMETER_NAME) //
.bind(knownRelationshipsIds) //
.to(Constants.NAME_OF_KNOWN_RELATIONSHIPS_PARAM) //
.bindAll(relationshipRemoveQuery.getCatalog().getParameters())
.run();
}

Expand Down Expand Up @@ -1023,6 +1034,7 @@ private <T> T processNestedRelations(
statementHolder = statementHolder.addProperty(Constants.NAME_OF_RELATIONSHIP_LIST_PARAM, plainRelationshipRows);
neo4jClient.query(renderer.render(statementHolder.getStatement()))
.bindAll(statementHolder.getProperties())
.bindAll(statementHolder.getStatement().getCatalog().getParameters())
.run();
} else if (relationshipDescription.hasRelationshipProperties()) {
if (!relationshipPropertiesRows.isEmpty()) {
Expand All @@ -1032,13 +1044,15 @@ private <T> T processNestedRelations(

neo4jClient.query(renderer.render(statementHolder.getStatement()))
.bindAll(statementHolder.getProperties())
.bindAll(statementHolder.getStatement().getCatalog().getParameters())
.run();
}
if (!newRelationshipPropertiesToStore.isEmpty()) {
CreateRelationshipStatementHolder statementHolder = neo4jMappingContext.createStatementForImperativeRelationshipsWithPropertiesBatch(true,
sourceEntity, relationshipDescription, newRelationshipPropertiesToStore, newRelationshipPropertiesRows, canUseElementId);
List<Object> all = new ArrayList<>(neo4jClient.query(renderer.render(statementHolder.getStatement()))
.bindAll(statementHolder.getProperties())
.bindAll(statementHolder.getStatement().getCatalog().getParameters())
.fetchAs(Object.class)
.mappedBy((t, r) -> IdentitySupport.mapperForRelatedIdValues(idProperty).apply(r))
.all());
Expand Down Expand Up @@ -1068,6 +1082,7 @@ private Optional<Object> getRelationshipId(Statement statement, Neo4jPersistentP
.to(Constants.FROM_ID_PARAMETER_NAME) //
.bind(toId) //
.to(Constants.TO_ID_PARAMETER_NAME) //
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Object.class)
.mappedBy((t, r) -> IdentitySupport.mapperForRelatedIdValues(idProperty).apply(r))
.one();
Expand Down Expand Up @@ -1126,9 +1141,11 @@ private Entity saveRelatedNode(Object entity, NodeDescription<?> targetNodeDescr
}
return tree;
});
var statement = cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer));
Optional<Entity> optionalSavedNode = neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer))))
.query(() -> renderer.render(statement))
.bind(entity).with(binderFunction)
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Entity.class)
.one();

Expand Down Expand Up @@ -1424,6 +1441,7 @@ private void iterateNextLevel(Collection<String> nodeIds, RelationshipDescriptio

neo4jClient.query(renderer.render(statement))
.bindAll(Collections.singletonMap(Constants.NAME_OF_IDS, TemplateSupport.convertToLongIdOrStringElementId(nodeIds)))
.bindAll(statement.getCatalog().getParameters())
.fetch()
.one()
.ifPresent(iterateAndMapNextLevel(relationshipsToRelatedNodes, relationshipDescription, nextPathStep));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -464,9 +464,11 @@ private <T> Mono<T> saveImpl(T instance, @Nullable Collection<PropertyFilter.Pro
neo4jMappingContext.getRequiredBinderFunctionFor((Class<T>) entityToBeSaved.getClass()));

boolean canUseElementId = TemplateSupport.rendererRendersElementId(renderer);
Mono<Entity> idMono = this.neo4jClient.query(() -> renderer.render(cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels, canUseElementId)))
var statement = cypherGenerator.prepareSaveOf(entityMetaData, dynamicLabels, canUseElementId);
Mono<Entity> idMono = this.neo4jClient.query(() -> renderer.render(statement))
.bind(entityToBeSaved)
.with(binderFunction)
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Entity.class)
.one()
.switchIfEmpty(Mono.defer(() -> {
Expand Down Expand Up @@ -498,10 +500,12 @@ private <T> Mono<Tuple2<T, DynamicLabels>> determineDynamicLabels(T entityToBeSa

PersistentPropertyAccessor<?> propertyAccessor = entityMetaData.getPropertyAccessor(entityToBeSaved);
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
var statementReturningDynamicLabels = cypherGenerator.createStatementReturningDynamicLabels(entityMetaData);
ReactiveNeo4jClient.RunnableSpec runnableQuery = neo4jClient
.query(() -> renderer.render(cypherGenerator.createStatementReturningDynamicLabels(entityMetaData)))
.query(() -> renderer.render(statementReturningDynamicLabels))
.bind(convertIdValues(idProperty, propertyAccessor.getProperty(idProperty)))
.to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels()).to(Constants.NAME_OF_STATIC_LABELS_PARAM);
.to(Constants.NAME_OF_ID).bind(entityMetaData.getStaticLabels()).to(Constants.NAME_OF_STATIC_LABELS_PARAM)
.bindAll(statementReturningDynamicLabels.getCatalog().getParameters());

if (entityMetaData.hasVersionProperty()) {
runnableQuery = runnableQuery
Expand Down Expand Up @@ -613,17 +617,18 @@ private <T> Flux<T> saveAllImpl(Iterable<T> instances, @Nullable Collection<Prop
List<Map<String, Object>> boundedEntityList = entitiesToBeSaved.stream()
.map(Tuple3::getT3) // extract PotentiallyModified
.map(binderFunction).collect(Collectors.toList());
return neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOfMultipleInstancesOf(entityMetaData)))
var statement = cypherGenerator.prepareSaveOfMultipleInstancesOf(entityMetaData);
return neo4jClient
.query(() -> renderer.render(statement))
.bind(boundedEntityList).to(Constants.NAME_OF_ENTITY_LIST_PARAM)
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Tuple2.class)
.mappedBy((t, r) -> Tuples.of(r.get(Constants.NAME_OF_ID), TemplateSupport.convertIdOrElementIdToString(r.get(Constants.NAME_OF_ELEMENT_ID))))
.all()
.collectMap(m -> (Value) m.getT1(), m -> (String) m.getT2());
}).flatMapMany(idToInternalIdMapping -> Flux.fromIterable(entitiesToBeSaved)
.concatMap(t -> {
PersistentPropertyAccessor<T> propertyAccessor = entityMetaData.getPropertyAccessor(t.getT3());
Neo4jPersistentProperty idProperty = entityMetaData.getRequiredIdProperty();
return processRelations(entityMetaData, propertyAccessor, t.getT2(),
ctx.get("stateMachine"),
ctx.get("knownRelIds"),
Expand All @@ -648,7 +653,9 @@ public <T> Mono<Void> deleteAllById(Iterable<?> ids, Class<T> domainType) {
return transactionalOperator.transactional(Mono.defer(() ->
this.neo4jClient.query(() -> renderer.render(statement))
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), ids))
.to(nameOfParameter).run().then()));
.to(nameOfParameter)
.bindAll(statement.getCatalog().getParameters())
.run().then()));
}

@Override
Expand All @@ -664,7 +671,9 @@ public <T> Mono<Void> deleteById(Object id, Class<T> domainType) {
return transactionalOperator.transactional(Mono.defer(() ->
this.neo4jClient.query(() -> renderer.render(statement))
.bind(convertIdValues(entityMetaData.getRequiredIdProperty(), id))
.to(nameOfParameter).run().then()));
.to(nameOfParameter)
.bindAll(statement.getCatalog().getParameters())
.run().then()));
}

@Override
Expand Down Expand Up @@ -970,7 +979,8 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
.bind(convertIdValues(sourceEntity.getIdProperty(), fromId)) //
.to(Constants.FROM_ID_PARAMETER_NAME) //
.bind(knownRelationshipsIds) //
.to(Constants.NAME_OF_KNOWN_RELATIONSHIPS_PARAM) //
.to(Constants.NAME_OF_KNOWN_RELATIONSHIPS_PARAM)
.bindAll(relationshipRemoveQuery.getCatalog().getParameters())
.run().checkpoint("delete relationships").then());
}

Expand Down Expand Up @@ -1077,6 +1087,7 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
.bind(idValue) //
.to(Constants.NAME_OF_KNOWN_RELATIONSHIP_PARAM) //
.bindAll(statementHolder.getProperties())
.bindAll(statementHolder.getStatement().getCatalog().getParameters())
.fetchAs(Object.class)
.mappedBy((t, r) -> IdentitySupport.mapperForRelatedIdValues(idProperty).apply(r))
.one()
Expand Down Expand Up @@ -1143,6 +1154,7 @@ private Mono<Object> getRelationshipId(Statement statement, Neo4jPersistentPrope
.to(Constants.FROM_ID_PARAMETER_NAME) //
.bind(toId) //
.to(Constants.TO_ID_PARAMETER_NAME) //
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Object.class)
.mappedBy((t, r) -> IdentitySupport.mapperForRelatedIdValues(idProperty).apply(r))
.one();
Expand All @@ -1156,11 +1168,13 @@ private Mono<Entity> loadRelatedNode(NodeDescription<?> targetNodeDescription, O
var queryFragmentsAndParameters = QueryFragmentsAndParameters.forFindById(targetPersistentEntity, convertIdValues(targetPersistentEntity.getRequiredIdProperty(), relatedInternalId));
var nodeName = Constants.NAME_OF_TYPED_ROOT_NODE.apply(targetNodeDescription).getValue();

var statement = cypherGenerator.prepareFindOf(targetNodeDescription, queryFragmentsAndParameters.getQueryFragments().getMatchOn(),
queryFragmentsAndParameters.getQueryFragments().getCondition()).returning(nodeName).build();
return neo4jClient
.query(() -> renderer.render(
cypherGenerator.prepareFindOf(targetNodeDescription, queryFragmentsAndParameters.getQueryFragments().getMatchOn(),
queryFragmentsAndParameters.getQueryFragments().getCondition()).returning(nodeName).build()))
statement))
.bindAll(queryFragmentsAndParameters.getParameters())
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Entity.class).mappedBy((t, r) -> r.get(nodeName).asNode())
.one();
}
Expand Down Expand Up @@ -1191,9 +1205,11 @@ private Mono<Entity> saveRelatedNode(Object relatedNode, Neo4jPersistentEntity<?
}
return tree;
});
var statement = cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer));
return neo4jClient
.query(() -> renderer.render(cypherGenerator.prepareSaveOf(targetNodeDescription, dynamicLabels, TemplateSupport.rendererRendersElementId(renderer))))
.query(() -> renderer.render(statement))
.bind(entity).with(binderFunction)
.bindAll(statement.getCatalog().getParameters())
.fetchAs(Entity.class)
.one();
}).switchIfEmpty(Mono.defer(() -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
import static org.neo4j.cypherdsl.core.Cypher.listBasedOn;
import static org.neo4j.cypherdsl.core.Cypher.literalOf;
import static org.neo4j.cypherdsl.core.Cypher.match;
import static org.neo4j.cypherdsl.core.Cypher.node;
import static org.neo4j.cypherdsl.core.Cypher.optionalMatch;
import static org.neo4j.cypherdsl.core.Cypher.parameter;

Expand Down Expand Up @@ -94,6 +93,14 @@ public enum CypherGenerator {
}
};

private static Node node(String primaryLabel, List<String> additionalLabels) {
var labels = Cypher.exactlyLabel(primaryLabel);
if (!additionalLabels.isEmpty()) {
labels = labels.conjunctionWith(Cypher.allLabels(Cypher.anonParameter(additionalLabels)));
}
return Cypher.node(labels);
}

/**
* Set function to be used to query either elementId or id.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,13 +27,15 @@
import java.util.stream.Stream;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.CsvSource;
import org.junit.jupiter.params.provider.MethodSource;
import org.mockito.Mockito;
import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.FunctionInvocation;
import org.neo4j.cypherdsl.core.Statement;
import org.neo4j.cypherdsl.core.renderer.Configuration;
import org.neo4j.cypherdsl.core.renderer.Dialect;
Expand All @@ -49,6 +51,12 @@
*/
class CypherGeneratorTest {

@BeforeAll
static void fixTheAbusOfASingleton() {
CypherGenerator.INSTANCE
.setElementIdOrIdFunction(n -> FunctionInvocation.create(() -> "elementId", n.getRequiredSymbolicName()));
}

@Test
void shouldCreateRelationshipCreationQueryWithLabelIfPresent() {
Neo4jPersistentEntity<?> persistentEntity = new Neo4jMappingContext().getPersistentEntity(Entity1.class);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import static org.neo4j.cypherdsl.core.Cypher.name;
import static org.neo4j.cypherdsl.core.Cypher.node;
import static org.neo4j.cypherdsl.core.Cypher.parameter;
import static org.neo4j.cypherdsl.core.Cypher.shortestPath;
import static org.neo4j.cypherdsl.core.Cypher.shortestK;

// end::domain-results-impl[]
import java.util.Collection;
Expand Down Expand Up @@ -82,7 +82,7 @@ public List<MovieEntity> findMoviesAlongShortestPath(PersonEntity from, PersonEn

var p1 = node("Person").withProperties("name", parameter("person1"));
var p2 = node("Person").withProperties("name", parameter("person2"));
var shortestPath = shortestPath("p").definedBy(
var shortestPath = shortestK(1).named("p").definedBy(
p1.relationshipBetween(p2).unbounded()
);
var p = shortestPath.getRequiredSymbolicName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@
import java.util.Map;

import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Tag;
import org.neo4j.driver.Driver;
import org.neo4j.driver.types.TypeSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.neo4j.test.Neo4jExtension;

@Tag(Neo4jExtension.NEEDS_VERSION_SUPPORTING_ELEMENT_ID)
abstract class AbstractCascadingTestBase {

@Autowired
Expand Down
Loading
Loading