Skip to content

Commit 5c16d10

Browse files
committed
GH-2526 - Find and map relationship in inheritance correctly.
Closes #2526
1 parent 6f19c80 commit 5c16d10

File tree

4 files changed

+260
-3
lines changed

4 files changed

+260
-3
lines changed

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -644,7 +644,7 @@ private void generateListFor(PropertyFilter.RelaxedPropertyPath parentPath, Neo4
644644

645645
String relationshipType = relationshipDescription.getType();
646646
String relationshipTargetName = relationshipDescription.generateRelatedNodesCollectionName(nodeDescription);
647-
String sourcePrimaryLabel = relationshipDescription.getSource().getMostAbstractParentLabel(relationshipDescription.getSource());
647+
String sourcePrimaryLabel = relationshipDescription.getSource().getMostAbstractParentLabel(nodeDescription);
648648
String targetPrimaryLabel = relationshipDescription.getTarget().getPrimaryLabel();
649649
List<String> targetAdditionalLabels = relationshipDescription.getTarget().getAdditionalLabels();
650650
String relationshipSymbolicName = sourcePrimaryLabel + RelationshipDescription.NAME_OF_RELATIONSHIP + targetPrimaryLabel;

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -541,7 +541,6 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
541541
Collection<Node> nodesFromResult) {
542542

543543
String typeOfRelationship = relationshipDescription.getType();
544-
String sourceLabel = relationshipDescription.getSource().getPrimaryLabel();
545544
String targetLabel = relationshipDescription.getTarget().getPrimaryLabel();
546545

547546
Neo4jPersistentEntity<?> genericTargetNodeDescription = (Neo4jPersistentEntity<?>) relationshipDescription
@@ -634,6 +633,7 @@ private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty p
634633
Object valueEntry = map(relatedEntity, concreteTargetNodeDescription, null, relationshipsFromResult, nodesFromResult);
635634

636635
if (relationshipDescription.hasRelationshipProperties()) {
636+
String sourceLabel = relationshipDescription.getSource().getMostAbstractParentLabel(baseDescription);
637637
String relationshipSymbolicName = sourceLabel
638638
+ RelationshipDescription.NAME_OF_RELATIONSHIP + targetLabel;
639639
Relationship relatedEntityRelationship = relatedEntity.get(relationshipSymbolicName)

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -116,9 +116,9 @@ public String getMostAbstractParentLabel(NodeDescription<?> mostAbstractNodeDesc
116116

117117
private NodeDescription<?> getMostAbstractParent(NodeDescription<?> mostAbstractNodeDescription) {
118118
if (mostAbstractNodeDescription.equals(this)) {
119+
// It is "me"
119120
return this;
120121
}
121-
// It could be "me"
122122
NodeDescription<?> mostAbstractParent = this;
123123
for (; /* Michael and me smiling at each other */ ;) {
124124
NodeDescription<?> parent = mostAbstractParent.getParentNodeDescription();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
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.gh2526;
17+
18+
import lombok.AccessLevel;
19+
import lombok.AllArgsConstructor;
20+
import lombok.Data;
21+
import lombok.EqualsAndHashCode;
22+
import lombok.NoArgsConstructor;
23+
import lombok.Setter;
24+
import lombok.Value;
25+
import lombok.With;
26+
import lombok.experimental.NonFinal;
27+
import lombok.experimental.SuperBuilder;
28+
import org.assertj.core.api.InstanceOfAssertFactories;
29+
import org.junit.jupiter.api.BeforeEach;
30+
import org.junit.jupiter.api.Test;
31+
import org.neo4j.driver.Driver;
32+
import org.neo4j.driver.Session;
33+
import org.springframework.beans.factory.annotation.Autowired;
34+
import org.springframework.context.annotation.Bean;
35+
import org.springframework.context.annotation.Configuration;
36+
import org.springframework.data.annotation.Immutable;
37+
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
38+
import org.springframework.data.neo4j.core.DatabaseSelectionProvider;
39+
import org.springframework.data.neo4j.core.schema.GeneratedValue;
40+
import org.springframework.data.neo4j.core.schema.Id;
41+
import org.springframework.data.neo4j.core.schema.Node;
42+
import org.springframework.data.neo4j.core.schema.Relationship;
43+
import org.springframework.data.neo4j.core.schema.RelationshipId;
44+
import org.springframework.data.neo4j.core.schema.RelationshipProperties;
45+
import org.springframework.data.neo4j.core.schema.TargetNode;
46+
import org.springframework.data.neo4j.core.support.UUIDStringGenerator;
47+
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
48+
import org.springframework.data.neo4j.core.transaction.Neo4jTransactionManager;
49+
import org.springframework.data.neo4j.repository.Neo4jRepository;
50+
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
51+
import org.springframework.data.neo4j.test.BookmarkCapture;
52+
import org.springframework.data.neo4j.test.Neo4jExtension;
53+
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
54+
import org.springframework.transaction.PlatformTransactionManager;
55+
import org.springframework.transaction.annotation.EnableTransactionManagement;
56+
57+
import java.util.Set;
58+
59+
import static org.assertj.core.api.Assertions.assertThat;
60+
import static org.assertj.core.api.Assertions.tuple;
61+
62+
/**
63+
* Test setup for GH-2526: Query creation of and picking the correct named relationship out of the result.
64+
*/
65+
@Neo4jIntegrationTest
66+
public class GH2526IT {
67+
68+
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
69+
70+
@BeforeEach
71+
void setupData(@Autowired Driver driver, @Autowired BookmarkCapture bookmarkCapture) {
72+
73+
try (Session session = driver.session()) {
74+
session.run("MATCH (n) DETACH DELETE n").consume();
75+
session.run(
76+
"CREATE (o1:Measurand {measurandId: 'o1'})\n" +
77+
"CREATE (acc1:AccountingMeasurementMeta:MeasurementMeta:BaseNodeEntity {nodeId: 'acc1'})\n" +
78+
"CREATE (m1:MeasurementMeta:BaseNodeEntity {nodeId: 'm1'})\n" +
79+
"CREATE (acc1)-[:USES{variable: 'A'}]->(m1)\n" +
80+
"CREATE (o1)-[:IS_MEASURED_BY{ manual: true }]->(acc1)\n"
81+
).consume();
82+
bookmarkCapture.seedWith(session.lastBookmark());
83+
}
84+
}
85+
86+
@Test
87+
void relationshipWillGetFoundInResultOfMultilevelInheritance(@Autowired BaseNodeRepository repository) {
88+
MeasurementProjection m = repository.findByNodeId("acc1", MeasurementProjection.class);
89+
assertThat(m).isNotNull();
90+
assertThat(m.getDataPoints()).isNotEmpty();
91+
assertThat(m).extracting(MeasurementProjection::getDataPoints, InstanceOfAssertFactories.collection(DataPoint.class))
92+
.extracting(DataPoint::isManual, DataPoint::getMeasurand).contains(tuple(true, new Measurand("o1")));
93+
}
94+
95+
interface BaseNodeFieldsProjection {
96+
String getNodeId();
97+
}
98+
99+
100+
interface MeasurementProjection extends BaseNodeFieldsProjection {
101+
Set<DataPoint> getDataPoints();
102+
Set<VariableProjection> getVariables();
103+
}
104+
105+
interface VariableProjection {
106+
BaseNodeFieldsProjection getMeasurement();
107+
String getVariable();
108+
}
109+
110+
111+
/**
112+
* Defining most concrete entity
113+
*/
114+
@Node
115+
@Data
116+
@Setter(AccessLevel.PRIVATE)
117+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
118+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
119+
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
120+
@SuperBuilder(toBuilder = true)
121+
public static class AccountingMeasurementMeta extends MeasurementMeta {
122+
123+
private String formula;
124+
125+
@Relationship(type = "WEIGHTS", direction = Relationship.Direction.OUTGOING)
126+
private MeasurementMeta baseMeasurement;
127+
}
128+
129+
/**
130+
* Defining base entity
131+
*/
132+
@Node
133+
@NonFinal
134+
@Data
135+
@Setter(AccessLevel.PRIVATE)
136+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
137+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
138+
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
139+
@SuperBuilder(toBuilder = true)
140+
public static class BaseNodeEntity {
141+
142+
@Id
143+
@GeneratedValue(UUIDStringGenerator.class)
144+
@EqualsAndHashCode.Include
145+
private String nodeId;
146+
}
147+
148+
/**
149+
* Target node
150+
*/
151+
@Node
152+
@Value
153+
@AllArgsConstructor
154+
@Immutable
155+
public static class Measurand {
156+
157+
@Id
158+
String measurandId;
159+
}
160+
161+
/**
162+
* Defining relationship to measurand
163+
*/
164+
@Node
165+
@Data
166+
@Setter(AccessLevel.PRIVATE)
167+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
168+
@AllArgsConstructor(access = AccessLevel.PROTECTED)
169+
@EqualsAndHashCode(callSuper = true, onlyExplicitlyIncluded = true)
170+
@SuperBuilder(toBuilder = true)
171+
public static class MeasurementMeta extends BaseNodeEntity {
172+
173+
@Relationship(type = "IS_MEASURED_BY", direction = Relationship.Direction.INCOMING)
174+
private Set<DataPoint> dataPoints;
175+
176+
@Relationship(type = "USES", direction = Relationship.Direction.OUTGOING)
177+
private Set<Variable> variables;
178+
}
179+
180+
/**
181+
* Second type of relationship
182+
*/
183+
@RelationshipProperties
184+
@Value
185+
@With
186+
@AllArgsConstructor
187+
@EqualsAndHashCode
188+
@Immutable
189+
public static class Variable {
190+
@RelationshipId
191+
Long id;
192+
193+
@TargetNode
194+
MeasurementMeta measurement;
195+
196+
String variable;
197+
198+
public static Variable create(MeasurementMeta measurement, String variable) {
199+
return new Variable(null, measurement, variable);
200+
}
201+
202+
@Override
203+
public String toString() {
204+
return variable + ": " + measurement.getNodeId();
205+
}
206+
}
207+
208+
/**
209+
* Relationship with properties between measurement and measurand
210+
*/
211+
@RelationshipProperties
212+
@Value
213+
@With
214+
@AllArgsConstructor
215+
@Immutable
216+
@EqualsAndHashCode(onlyExplicitlyIncluded = true)
217+
public static class DataPoint {
218+
219+
@RelationshipId
220+
Long id;
221+
222+
boolean manual;
223+
224+
@TargetNode
225+
@EqualsAndHashCode.Include
226+
Measurand measurand;
227+
}
228+
229+
interface BaseNodeRepository extends Neo4jRepository<BaseNodeEntity, String> {
230+
<R> R findByNodeId(String nodeIds, Class<R> clazz);
231+
}
232+
233+
@Configuration
234+
@EnableTransactionManagement
235+
@EnableNeo4jRepositories(considerNestedRepositories = true)
236+
static class Config extends AbstractNeo4jConfig {
237+
238+
@Bean
239+
public BookmarkCapture bookmarkCapture() {
240+
return new BookmarkCapture();
241+
}
242+
243+
@Override
244+
public PlatformTransactionManager transactionManager(
245+
Driver driver, DatabaseSelectionProvider databaseNameProvider) {
246+
247+
BookmarkCapture bookmarkCapture = bookmarkCapture();
248+
return new Neo4jTransactionManager(driver, databaseNameProvider,
249+
Neo4jBookmarkManager.create(bookmarkCapture));
250+
}
251+
252+
@Bean
253+
public Driver driver() {
254+
return neo4jConnectionSupport.getDriver();
255+
}
256+
}
257+
}

0 commit comments

Comments
 (0)