Skip to content

Commit 911cd92

Browse files
committed
GH-2533 - Don't skip the skip on dynamic relationship.
Closes #2533
1 parent 6d5b707 commit 911cd92

File tree

5 files changed

+346
-6
lines changed

5 files changed

+346
-6
lines changed

src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -709,8 +709,8 @@ private <T> T processNestedRelations(
709709
RelationshipDescription relationshipDescription = relationshipContext.getRelationship();
710710

711711
PropertyFilter.RelaxedPropertyPath currentPropertyPath = previousPath.append(relationshipDescription.getFieldName());
712-
boolean dynamicRelationship = relationshipDescription.isDynamic();
713-
if (!includeProperty.isNotFiltering() && !dynamicRelationship && !includeProperty.contains(currentPropertyPath)) {
712+
713+
if (!includeProperty.isNotFiltering() && !includeProperty.contains(currentPropertyPath)) {
714714
return;
715715
}
716716
Neo4jPersistentProperty idProperty;
@@ -839,7 +839,7 @@ private <T> T processNestedRelations(
839839
}
840840

841841
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
842-
processNestedRelations(targetEntity, targetPropertyAccessor, isEntityNew, stateMachine, dynamicRelationship ? PropertyFilter.acceptAll() : includeProperty, currentPropertyPath);
842+
processNestedRelations(targetEntity, targetPropertyAccessor, isEntityNew, stateMachine, includeProperty, currentPropertyPath);
843843
}
844844

845845
Object potentiallyRecreatedNewRelatedObject = MappingSupport.getRelationshipOrRelationshipPropertiesObject(neo4jMappingContext,

src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -840,8 +840,8 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
840840
RelationshipDescription relationshipDescription = relationshipContext.getRelationship();
841841

842842
PropertyFilter.RelaxedPropertyPath currentPropertyPath = previousPath.append(relationshipDescription.getFieldName());
843-
boolean dynamicRelationship = relationshipDescription.isDynamic();
844-
if (!includeProperty.isNotFiltering() && !dynamicRelationship && !includeProperty.contains(currentPropertyPath)) {
843+
844+
if (!includeProperty.isNotFiltering() && !includeProperty.contains(currentPropertyPath)) {
845845
return;
846846
}
847847
Neo4jPersistentProperty idProperty;
@@ -978,7 +978,7 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
978978

979979
Mono<Object> nestedRelationshipsSignal = null;
980980
if (processState != ProcessState.PROCESSED_ALL_VALUES) {
981-
nestedRelationshipsSignal = processNestedRelations(targetEntity, targetPropertyAccessor, targetEntity.isNew(newRelatedObject), stateMachine, dynamicRelationship ? PropertyFilter.acceptAll() : includeProperty, currentPropertyPath);
981+
nestedRelationshipsSignal = processNestedRelations(targetEntity, targetPropertyAccessor, targetEntity.isNew(newRelatedObject), stateMachine, includeProperty, currentPropertyPath);
982982
}
983983

984984
Mono<Object> getRelationshipOrRelationshipPropertiesObject = Mono.fromSupplier(() -> MappingSupport.getRelationshipOrRelationshipPropertiesObject(
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
/*
2+
* Copyright 2011-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.issues.gh2533;
17+
18+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
19+
import org.springframework.data.neo4j.core.schema.Id;
20+
import org.springframework.data.neo4j.core.schema.Node;
21+
import org.springframework.data.neo4j.core.schema.Relationship;
22+
import org.springframework.data.neo4j.core.schema.RelationshipId;
23+
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
24+
import org.springframework.data.neo4j.core.schema.TargetNode;
25+
26+
import java.util.List;
27+
import java.util.Map;
28+
29+
/**
30+
* Collection of entities for GH2533.
31+
*/
32+
public class EntitiesAndProjections {
33+
34+
/**
35+
* Entity
36+
*/
37+
@Node
38+
public static class GH2533Entity {
39+
@Id
40+
@GeneratedValue
41+
public Long id;
42+
43+
@Relationship
44+
public Map<String, List<GH2533Relationship>> relationships;
45+
}
46+
47+
/**
48+
* Relationship
49+
*/
50+
@RelationshipProperties
51+
public static class GH2533Relationship {
52+
@RelationshipId
53+
public Long id;
54+
55+
@TargetNode
56+
public GH2533Entity target;
57+
}
58+
59+
/**
60+
* Projection breaking the infinite lopp
61+
*/
62+
public interface GH2533EntityWithoutRelationship {
63+
Long getId();
64+
65+
String getName();
66+
}
67+
68+
/**
69+
* Projection with one level of relationship
70+
*/
71+
public interface GH2533EntityNodeWithOneLevelLinks {
72+
Long getId();
73+
74+
String getName();
75+
76+
Map<String, List<GH2533RelationshipWithoutTargetRelationships>> getRelationships();
77+
}
78+
79+
/**
80+
* Projection of the relationship properties
81+
*/
82+
public interface GH2533RelationshipWithoutTargetRelationships {
83+
Long getId();
84+
85+
boolean isActive();
86+
87+
GH2533EntityWithoutRelationship getTarget();
88+
}
89+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
/*
2+
* Copyright 2011-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.issues.gh2533;
17+
18+
import org.junit.jupiter.api.BeforeEach;
19+
import org.junit.jupiter.api.Test;
20+
import org.neo4j.driver.Driver;
21+
import org.neo4j.driver.Session;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
26+
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
27+
import org.springframework.data.neo4j.core.Neo4jTemplate;
28+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
29+
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
30+
import org.springframework.data.neo4j.repository.Neo4jRepository;
31+
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
32+
import org.springframework.data.neo4j.repository.query.Query;
33+
import org.springframework.data.neo4j.test.BookmarkCapture;
34+
import org.springframework.data.neo4j.test.Neo4jExtension;
35+
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
36+
import org.springframework.data.repository.query.Param;
37+
import org.springframework.transaction.PlatformTransactionManager;
38+
import org.springframework.transaction.annotation.EnableTransactionManagement;
39+
40+
import java.util.Arrays;
41+
import java.util.Collections;
42+
import java.util.Optional;
43+
44+
import static org.assertj.core.api.Assertions.assertThat;
45+
46+
/**
47+
* Test for projection with dynamic relationships.
48+
*/
49+
@Neo4jIntegrationTest
50+
public class GH2533IT {
51+
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
52+
53+
@Autowired
54+
private GH2533Repository repository;
55+
56+
@Autowired
57+
private Neo4jTemplate neo4jTemplate;
58+
59+
@BeforeEach
60+
void setupData(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) {
61+
62+
try (Session session = driver.session()) {
63+
session.run("MATCH (n) DETACH DELETE n").consume();
64+
bookmarkCapture.seedWith(session.lastBookmark());
65+
}
66+
}
67+
68+
@Test // GH-2533
69+
void projectionWorksForDynamicRelationshipsOnSave() {
70+
EntitiesAndProjections.GH2533Entity rootEntity = createData();
71+
72+
rootEntity = repository.findByIdWithLevelOneLinks(rootEntity.id).get();
73+
74+
// this would cause the rootEntity -> child -X-> child relationship to get removed (X).
75+
neo4jTemplate.saveAs(rootEntity, EntitiesAndProjections.GH2533EntityNodeWithOneLevelLinks.class);
76+
77+
EntitiesAndProjections.GH2533Entity savedEntity = neo4jTemplate.findById(rootEntity.id, EntitiesAndProjections.GH2533Entity.class).get();
78+
79+
assertThat(savedEntity.relationships).isNotEmpty();
80+
assertThat(savedEntity.relationships.get("has_relationship_with")).isNotEmpty();
81+
assertThat(savedEntity.relationships.get("has_relationship_with").get(0).target).isNotNull();
82+
assertThat(savedEntity.relationships.get("has_relationship_with").get(0).target.relationships).isNotEmpty();
83+
}
84+
85+
private EntitiesAndProjections.GH2533Entity createData() {
86+
EntitiesAndProjections.GH2533Entity n1 = new EntitiesAndProjections.GH2533Entity();
87+
EntitiesAndProjections.GH2533Entity n2 = new EntitiesAndProjections.GH2533Entity();
88+
EntitiesAndProjections.GH2533Entity n3 = new EntitiesAndProjections.GH2533Entity();
89+
90+
EntitiesAndProjections.GH2533Relationship r1 = new EntitiesAndProjections.GH2533Relationship();
91+
EntitiesAndProjections.GH2533Relationship r2 = new EntitiesAndProjections.GH2533Relationship();
92+
93+
r1.target = n2;
94+
r2.target = n3;
95+
96+
// Add relationships
97+
n1.relationships = Collections.singletonMap("has_relationship_with", Arrays.asList(r1));
98+
n2.relationships = Collections.singletonMap("has_relationship_with", Arrays.asList(r2));
99+
100+
return repository.save(n1);
101+
}
102+
103+
104+
interface GH2533Repository extends Neo4jRepository<EntitiesAndProjections.GH2533Entity, Long> {
105+
@Query("MATCH p=(n)-[*0..1]->(m) WHERE id(n)=$id RETURN n, collect(relationships(p)), collect(m);")
106+
Optional<EntitiesAndProjections.GH2533Entity> findByIdWithLevelOneLinks(@Param("id") Long id);
107+
}
108+
109+
@Configuration
110+
@EnableTransactionManagement
111+
@EnableNeo4jRepositories(considerNestedRepositories = true)
112+
static class Config extends AbstractNeo4jConfig {
113+
114+
@Bean
115+
public BookmarkCapture bookmarkCapture() {
116+
return new BookmarkCapture();
117+
}
118+
119+
@Override
120+
public PlatformTransactionManager transactionManager(
121+
Driver driver, DatabaseSelectionProvider databaseNameProvider) {
122+
123+
BookmarkCapture bookmarkCapture = bookmarkCapture();
124+
return new Neo4jTransactionManager(driver, databaseNameProvider,
125+
Neo4jBookmarkManager.create(bookmarkCapture));
126+
}
127+
128+
@Bean
129+
public Driver driver() {
130+
return neo4jConnectionSupport.getDriver();
131+
}
132+
}
133+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
/*
2+
* Copyright 2011-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.integration.issues.gh2533;
17+
18+
import org.junit.jupiter.api.BeforeEach;
19+
import org.junit.jupiter.api.Test;
20+
import org.neo4j.driver.Driver;
21+
import org.neo4j.driver.Session;
22+
import org.springframework.beans.factory.annotation.Autowired;
23+
import org.springframework.context.annotation.Bean;
24+
import org.springframework.context.annotation.Configuration;
25+
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
26+
import org.springframework.data.neo4j.core.ReactiveNeo4jTemplate;
27+
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
28+
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
29+
import org.springframework.data.neo4j.repository.query.Query;
30+
import org.springframework.data.neo4j.test.BookmarkCapture;
31+
import org.springframework.data.neo4j.test.Neo4jExtension;
32+
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
33+
import org.springframework.data.repository.query.Param;
34+
import reactor.core.publisher.Mono;
35+
import reactor.test.StepVerifier;
36+
37+
import java.util.Arrays;
38+
import java.util.Collections;
39+
40+
import static org.assertj.core.api.Assertions.assertThat;
41+
42+
/**
43+
* Test for projection with dynamic relationships.
44+
*/
45+
@Neo4jIntegrationTest
46+
public class GH2533ReactiveIT {
47+
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
48+
49+
@Autowired
50+
private ReactiveGH2533Repository repository;
51+
52+
@Autowired
53+
private ReactiveNeo4jTemplate neo4jTemplate;
54+
55+
@BeforeEach
56+
void setupData(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) {
57+
58+
try (Session session = driver.session()) {
59+
session.run("MATCH (n) DETACH DELETE n").consume();
60+
bookmarkCapture.seedWith(session.lastBookmark());
61+
}
62+
}
63+
64+
@Test // GH-2533
65+
void projectionWorksForDynamicRelationshipsOnSave() {
66+
createData()
67+
.flatMap(rootEntity -> repository.findByIdWithLevelOneLinks(rootEntity.id))
68+
.flatMap(rootEntity -> neo4jTemplate.saveAs(rootEntity, EntitiesAndProjections.GH2533EntityNodeWithOneLevelLinks.class))
69+
.flatMap(rootEntity -> neo4jTemplate.findById(rootEntity.getId(), EntitiesAndProjections.GH2533Entity.class))
70+
.as(StepVerifier::create)
71+
.assertNext(savedEntity -> {
72+
assertThat(savedEntity.relationships).isNotEmpty();
73+
assertThat(savedEntity.relationships.get("has_relationship_with")).isNotEmpty();
74+
assertThat(savedEntity.relationships.get("has_relationship_with").get(0).target).isNotNull();
75+
assertThat(savedEntity.relationships.get("has_relationship_with").get(0).target.relationships).isNotEmpty();
76+
})
77+
.verifyComplete();
78+
}
79+
80+
private Mono<EntitiesAndProjections.GH2533Entity> createData() {
81+
EntitiesAndProjections.GH2533Entity n1 = new EntitiesAndProjections.GH2533Entity();
82+
EntitiesAndProjections.GH2533Entity n2 = new EntitiesAndProjections.GH2533Entity();
83+
EntitiesAndProjections.GH2533Entity n3 = new EntitiesAndProjections.GH2533Entity();
84+
85+
EntitiesAndProjections.GH2533Relationship r1 = new EntitiesAndProjections.GH2533Relationship();
86+
EntitiesAndProjections.GH2533Relationship r2 = new EntitiesAndProjections.GH2533Relationship();
87+
88+
r1.target = n2;
89+
r2.target = n3;
90+
91+
// Add relationships
92+
n1.relationships = Collections.singletonMap("has_relationship_with", Arrays.asList(r1));
93+
n2.relationships = Collections.singletonMap("has_relationship_with", Arrays.asList(r2));
94+
95+
return repository.save(n1);
96+
}
97+
98+
99+
interface ReactiveGH2533Repository extends ReactiveNeo4jRepository<EntitiesAndProjections.GH2533Entity, Long> {
100+
@Query("MATCH p=(n)-[*0..1]->(m) WHERE id(n)=$id RETURN n, collect(relationships(p)), collect(m);")
101+
Mono<EntitiesAndProjections.GH2533Entity> findByIdWithLevelOneLinks(@Param("id") Long id);
102+
}
103+
104+
@Configuration
105+
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
106+
static class Config extends AbstractReactiveNeo4jConfig {
107+
108+
@Bean
109+
public BookmarkCapture bookmarkCapture() {
110+
return new BookmarkCapture();
111+
}
112+
113+
@Bean
114+
public Driver driver() {
115+
return neo4jConnectionSupport.getDriver();
116+
}
117+
}
118+
}

0 commit comments

Comments
 (0)