Skip to content

Commit e8c2161

Browse files
GH-2289 - Don’t use the fromId with obverse to stop traversing relationships.
The previous check used the obverse combined with the fromId to check if traversal can be stopped or not. This is wrong, we would need to combine obverse with targetId, which we may not have at the moment. This change simplifies the check and only looks at visited relationships from source to somewhere else to avoid additional rounds. This fixes #2289.
1 parent b882a12 commit e8c2161

File tree

8 files changed

+351
-6
lines changed

8 files changed

+351
-6
lines changed

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -647,7 +647,6 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
647647
rawValue);
648648

649649
RelationshipDescription relationshipDescription = relationshipContext.getRelationship();
650-
RelationshipDescription relationshipDescriptionObverse = relationshipDescription.getRelationshipObverse();
651650

652651
if (!includeProperty.test(relationshipDescription.getFieldName())) {
653652
return;
@@ -661,7 +660,7 @@ private <T> T processNestedRelations(Neo4jPersistentEntity<?> sourceEntity, Pers
661660
}
662661

663662
// break recursive procession and deletion of previously created relationships
664-
ProcessState processState = stateMachine.getStateOf(fromId, relationshipDescriptionObverse, relatedValuesToStore);
663+
ProcessState processState = stateMachine.getStateOf(fromId, relationshipDescription, relatedValuesToStore);
665664
if (processState == ProcessState.PROCESSED_ALL_RELATIONSHIPS || processState == ProcessState.PROCESSED_BOTH) {
666665
return;
667666
}

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

+1-2
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,6 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
733733
rawValue);
734734

735735
RelationshipDescription relationshipDescription = relationshipContext.getRelationship();
736-
RelationshipDescription relationshipDescriptionObverse = relationshipDescription.getRelationshipObverse();
737736

738737
if (!includeProperty.test(relationshipDescription.getFieldName())) {
739738
return;
@@ -747,7 +746,7 @@ private <T> Mono<T> processNestedRelations(Neo4jPersistentEntity<?> sourceEntity
747746
}
748747

749748
// break recursive procession and deletion of previously created relationships
750-
ProcessState processState = stateMachine.getStateOf(fromId, relationshipDescriptionObverse, relatedValuesToStore);
749+
ProcessState processState = stateMachine.getStateOf(fromId, relationshipDescription, relatedValuesToStore);
751750
if (processState == ProcessState.PROCESSED_ALL_RELATIONSHIPS || processState == ProcessState.PROCESSED_BOTH) {
752751
return;
753752
}

src/main/java/org/springframework/data/neo4j/core/mapping/DefaultRelationshipDescription.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,6 @@ public boolean equals(Object o) {
138138

139139
@Override
140140
public int hashCode() {
141-
return Objects.hash(type, type, fieldName, target, source, direction);
141+
return Objects.hash(fieldName, type, target, source, direction);
142142
}
143-
144143
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
/*
2+
* Copyright 2011-2021 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.gh2289;
17+
18+
import org.assertj.core.api.Assertions;
19+
import org.junit.jupiter.api.BeforeAll;
20+
import org.junit.jupiter.api.RepeatedTest;
21+
import org.neo4j.driver.Driver;
22+
import org.neo4j.driver.Session;
23+
import org.neo4j.driver.Transaction;
24+
import org.springframework.beans.factory.annotation.Autowired;
25+
import org.springframework.context.annotation.Bean;
26+
import org.springframework.context.annotation.Configuration;
27+
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
28+
import org.springframework.data.neo4j.repository.Neo4jRepository;
29+
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
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.stereotype.Repository;
34+
import org.springframework.transaction.annotation.EnableTransactionManagement;
35+
36+
/**
37+
* @author Michael J. Simons
38+
*/
39+
@Neo4jIntegrationTest
40+
class GH2289IT {
41+
42+
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
43+
44+
@BeforeAll
45+
protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) {
46+
try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig());
47+
Transaction transaction = session.beginTransaction();
48+
) {
49+
transaction.run("MATCH (n) detach delete n");
50+
transaction.commit();
51+
bookmarkCapture.seedWith(session.lastBookmark());
52+
}
53+
}
54+
55+
@RepeatedTest(23)
56+
void testNewRelation(@Autowired SkuRepository skuRepo) {
57+
Sku a = skuRepo.save(new Sku(0L, "A"));
58+
Sku b = skuRepo.save(new Sku(1L, "B"));
59+
Sku c = skuRepo.save(new Sku(2L, "C"));
60+
Sku d = skuRepo.save(new Sku(3L, "D"));
61+
62+
a.rangeRelationTo(b, 1, 1, RelationType.MULTIPLICATIVE);
63+
a.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
64+
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
65+
a = skuRepo.save(a);
66+
67+
Assertions.assertThat(a.getRangeRelationsOut()).hasSize(3);
68+
b = skuRepo.findById(b.getId()).get();
69+
Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1);
70+
71+
b.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
72+
b = skuRepo.save(b);
73+
Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1);
74+
Assertions.assertThat(b.getRangeRelationsOut()).hasSize(1);
75+
}
76+
77+
@Repository
78+
public interface SkuRepository extends Neo4jRepository<Sku, Long> {
79+
}
80+
81+
@Configuration
82+
@EnableTransactionManagement
83+
@EnableNeo4jRepositories(considerNestedRepositories = true)
84+
static class Config extends AbstractNeo4jConfig {
85+
86+
@Bean
87+
public BookmarkCapture bookmarkCapture() {
88+
return new BookmarkCapture();
89+
}
90+
91+
@Bean
92+
public Driver driver() {
93+
94+
return neo4jConnectionSupport.getDriver();
95+
}
96+
}
97+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2011-2021 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.gh2289;
17+
18+
import lombok.Data;
19+
20+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
21+
import org.springframework.data.neo4j.core.schema.Id;
22+
import org.springframework.data.neo4j.core.schema.Property;
23+
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
24+
import org.springframework.data.neo4j.core.schema.TargetNode;
25+
26+
/**
27+
* @author Michael J. Simons
28+
*/
29+
@Data // lombok
30+
@RelationshipProperties
31+
public class RangeRelation {
32+
@Id @GeneratedValue private Long id;
33+
34+
@Property private double minDelta;
35+
@Property private double maxDelta;
36+
@Property private RelationType relationType;
37+
38+
@TargetNode private Sku targetSku;
39+
40+
public RangeRelation(Sku targetSku, double minDelta, double maxDelta, RelationType relationType) {
41+
this.targetSku = targetSku;
42+
this.minDelta = minDelta;
43+
this.maxDelta = maxDelta;
44+
this.relationType = relationType;
45+
}
46+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2011-2021 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.gh2289;
17+
18+
import reactor.test.StepVerifier;
19+
20+
import java.util.concurrent.atomic.AtomicLong;
21+
import java.util.concurrent.atomic.AtomicReference;
22+
23+
import org.assertj.core.api.Assertions;
24+
import org.junit.jupiter.api.BeforeAll;
25+
import org.junit.jupiter.api.RepeatedTest;
26+
import org.junit.jupiter.api.Tag;
27+
import org.neo4j.driver.Driver;
28+
import org.neo4j.driver.Session;
29+
import org.neo4j.driver.Transaction;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.context.annotation.Bean;
32+
import org.springframework.context.annotation.Configuration;
33+
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
34+
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
35+
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
36+
import org.springframework.data.neo4j.test.BookmarkCapture;
37+
import org.springframework.data.neo4j.test.Neo4jExtension;
38+
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
39+
import org.springframework.stereotype.Repository;
40+
import org.springframework.transaction.annotation.EnableTransactionManagement;
41+
42+
/**
43+
* @author Michael J. Simons
44+
*/
45+
@Neo4jIntegrationTest
46+
@Tag(Neo4jExtension.NEEDS_REACTIVE_SUPPORT)
47+
class ReactiveGH2289IT {
48+
49+
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
50+
51+
@BeforeAll
52+
protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) {
53+
try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig());
54+
Transaction transaction = session.beginTransaction();
55+
) {
56+
transaction.run("MATCH (n) detach delete n");
57+
transaction.commit();
58+
bookmarkCapture.seedWith(session.lastBookmark());
59+
}
60+
}
61+
62+
@RepeatedTest(23)
63+
void testNewRelation(@Autowired SkuRepository skuRepo) {
64+
65+
AtomicLong bId = new AtomicLong();
66+
AtomicReference<Sku> cRef = new AtomicReference<>();
67+
skuRepo.save(new Sku(0L, "A"))
68+
.zipWith(skuRepo.save(new Sku(1L, "B")))
69+
.zipWith(skuRepo.save(new Sku(2L, "C")))
70+
.zipWith(skuRepo.save(new Sku(3L, "D"))).flatMap(t -> {
71+
Sku a = t.getT1().getT1().getT1();
72+
Sku b = t.getT1().getT1().getT2();
73+
Sku c = t.getT1().getT2();
74+
Sku d = t.getT2();
75+
76+
bId.set(b.getId());
77+
cRef.set(c);
78+
a.rangeRelationTo(b, 1, 1, RelationType.MULTIPLICATIVE);
79+
a.rangeRelationTo(c, 1, 1, RelationType.MULTIPLICATIVE);
80+
a.rangeRelationTo(d, 1, 1, RelationType.MULTIPLICATIVE);
81+
return skuRepo.save(a);
82+
}).as(StepVerifier::create)
83+
.expectNextMatches(a -> a.getRangeRelationsOut().size() == 3)
84+
.verifyComplete();
85+
86+
skuRepo.findById(bId.get())
87+
.doOnNext(b -> Assertions.assertThat(b.getRangeRelationsIn()).hasSize(1))
88+
.flatMap(b -> {
89+
b.rangeRelationTo(cRef.get(), 1, 1, RelationType.MULTIPLICATIVE);
90+
return skuRepo.save(b);
91+
})
92+
.as(StepVerifier::create)
93+
.expectNextMatches(a -> a.getRangeRelationsIn().size() == 1 && a.getRangeRelationsOut().size() == 1)
94+
.verifyComplete();
95+
}
96+
97+
@Repository
98+
public interface SkuRepository extends ReactiveNeo4jRepository<Sku, Long> {
99+
}
100+
101+
@Configuration
102+
@EnableTransactionManagement
103+
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
104+
static class Config extends AbstractReactiveNeo4jConfig {
105+
106+
@Bean
107+
public BookmarkCapture bookmarkCapture() {
108+
return new BookmarkCapture();
109+
}
110+
111+
@Bean
112+
public Driver driver() {
113+
114+
return neo4jConnectionSupport.getDriver();
115+
}
116+
}
117+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2011-2021 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.gh2289;
17+
18+
/**
19+
* @author Michael J. Simons
20+
*/
21+
public enum RelationType {
22+
MULTIPLICATIVE
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
/*
2+
* Copyright 2011-2021 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.gh2289;
17+
18+
import lombok.Getter;
19+
import lombok.Setter;
20+
21+
import java.util.HashSet;
22+
import java.util.Set;
23+
24+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
25+
import org.springframework.data.neo4j.core.schema.Id;
26+
import org.springframework.data.neo4j.core.schema.Node;
27+
import org.springframework.data.neo4j.core.schema.Property;
28+
import org.springframework.data.neo4j.core.schema.Relationship;
29+
30+
/**
31+
* @author Michael J. Simons
32+
*/
33+
@Node("SKU")
34+
@Getter // lombok
35+
@Setter
36+
public class Sku {
37+
38+
@Id @GeneratedValue
39+
private Long id;
40+
41+
@Property("number")
42+
private Long number;
43+
44+
@Property("name")
45+
private String name;
46+
47+
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.OUTGOING)
48+
private Set<RangeRelation> rangeRelationsOut = new HashSet<>();
49+
50+
@Relationship(type = "RANGE_RELATION_TO", direction = Relationship.Direction.INCOMING)
51+
private Set<RangeRelation> rangeRelationsIn = new HashSet<>();
52+
53+
public Sku(Long number, String name) {
54+
this.number = number;
55+
this.name = name;
56+
}
57+
58+
public RangeRelation rangeRelationTo(Sku sku, double minDelta, double maxDelta, RelationType relationType) {
59+
RangeRelation relationOut = new RangeRelation(sku, minDelta, maxDelta, relationType);
60+
RangeRelation relationIn = new RangeRelation(this, minDelta, maxDelta, relationType);
61+
rangeRelationsOut.add(relationOut);
62+
sku.rangeRelationsIn.add(relationIn);
63+
return relationOut;
64+
}
65+
}

0 commit comments

Comments
 (0)