Skip to content

Commit 4de185e

Browse files
GH-2537 - Fix serialization of the target node of relationship property entities.
This should have gone into a separate field in parallel to properties, not into the properties itself. Fixes #2537 and also adds a test for the specific question asked in the ticket.
1 parent 20e6bc9 commit 4de185e

File tree

4 files changed

+79
-15
lines changed

4 files changed

+79
-15
lines changed

src/main/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriter.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -114,12 +114,10 @@ Map<String, Object> writeImpl(Object source, Map<String, Object> sink, Set<Objec
114114
addLabels(sink, entity, propertyAccessor);
115115
addRelations(sink, entity, propertyAccessor, seenObjects);
116116
if (initialObject && entity.isRelationshipPropertiesEntity()) {
117-
Map<String, Object> propertyMap = (Map<String, Object>) sink.get(Constants.NAME_OF_PROPERTIES_PARAM);
118117
entity.doWithProperties((PropertyHandler<Neo4jPersistentProperty>) p -> {
119118
if (p.isAnnotationPresent(TargetNode.class)) {
120-
Map<String, Object> target = this.writeImpl(propertyAccessor.getProperty(p), new HashMap<>(), seenObjects, false);
121-
propertyMap.put("__target__", Values.value(target));
122-
return;
119+
Value target = Values.value(this.writeImpl(propertyAccessor.getProperty(p), new HashMap<>(), seenObjects, false));
120+
sink.put("__target__", target);
123121
}
124122
});
125123
}

src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/GH2323IT.java

+69-7
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,12 @@
1919

2020
import java.util.Arrays;
2121
import java.util.List;
22+
import java.util.Optional;
2223
import java.util.stream.Collectors;
2324

2425
import org.junit.jupiter.api.BeforeAll;
2526
import org.junit.jupiter.api.RepeatedTest;
27+
import org.junit.jupiter.api.BeforeEach;
2628
import org.junit.jupiter.api.Test;
2729
import org.neo4j.driver.Driver;
2830
import org.neo4j.driver.Session;
@@ -69,6 +71,21 @@ protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) {
6971
}
7072
}
7173

74+
@BeforeEach
75+
protected void removeRelationships(@Autowired BookmarkCapture bookmarkCapture) {
76+
try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig());
77+
Transaction transaction = session.beginTransaction();
78+
) {
79+
transaction.run("MATCH ()- [r:KNOWS]-() DELETE r").consume();
80+
transaction.run("MATCH (n:Language) DELETE n").consume();
81+
transaction.run("MATCH (n:Person {name: 'Gerrit'}) DETACH DELETE n").consume();
82+
transaction.run("unwind ['German', 'English'] as name create (n:Language {name: name}) return name")
83+
.consume();
84+
transaction.commit();
85+
bookmarkCapture.seedWith(session.lastBookmark());
86+
}
87+
}
88+
7289
@Test // GH-2323
7390
void listOfRelationshipPropertiesShouldBeUnwindable(@Autowired PersonService personService) {
7491
Person person = personService.updateRel(personId, Arrays.asList("German"));
@@ -84,10 +101,14 @@ void listOfRelationshipPropertiesShouldBeUnwindable(@Autowired PersonService per
84101
void dontMixRelatedNodes(@Autowired PersonRepository repository, @Autowired BookmarkCapture bookmarkCapture) {
85102
String id;
86103
try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig());
87-
Transaction transaction = session.beginTransaction();
104+
Transaction transaction = session.beginTransaction();
88105
) {
89-
id = transaction.run("CREATE (n:Person {id:randomUUID(), name: 'Gerrit'})-[:KNOWS]->(:Language{name:'English'}) return n.id").single().get(0).asString();
90-
transaction.run("MATCH (n:Person {name: 'Gerrit'}) MERGE (n)-[:MOTHER_TONGUE_IS]->(:Language{name:'German'})").consume();
106+
id = transaction.run(
107+
"CREATE (n:Person {id:randomUUID(), name: 'Gerrit'})-[:KNOWS]->(:Language{name:'English'}) return n.id")
108+
.single().get(0).asString();
109+
transaction.run(
110+
"MATCH (n:Person {name: 'Gerrit'}) MERGE (n)-[:MOTHER_TONGUE_IS]->(:Language{name:'German'})")
111+
.consume();
91112
transaction.commit();
92113
bookmarkCapture.seedWith(session.lastBookmark());
93114

@@ -101,14 +122,40 @@ void dontMixRelatedNodes(@Autowired PersonRepository repository, @Autowired Book
101122
}
102123
}
103124

125+
@Test // GH-2537
126+
void ensureRelationshipsAreSerialized(@Autowired PersonService personService) {
127+
128+
Optional<Person> optionalPerson = personService.updateRel2(personId, Arrays.asList("German"));
129+
assertThat(optionalPerson).isPresent().hasValueSatisfying(person -> {
130+
assertThat(person.getKnownLanguages()).hasSize(1);
131+
assertThat(person.getKnownLanguages()).first().satisfies(knows -> {
132+
assertThat(knows.getDescription()).isEqualTo("Some description");
133+
assertThat(knows.getLanguage()).extracting(Language::getName).isEqualTo("German");
134+
});
135+
});
136+
}
137+
104138
@Repository
105139
public interface PersonRepository extends Neo4jRepository<Person, String> {
106140

107-
@Query("UNWIND $relations As rel WITH rel " +
108-
"CREATE (f:Person {id: $from}) - [r:KNOWS {description: rel.__properties__.description}] -> (t:Language {name: rel.__properties__.__target__.__id__}) "
109-
+
110-
"RETURN f, collect(r), collect(t)")
141+
// Using separate id and than relationships on top level
142+
@Query(""
143+
+ "UNWIND $relations As rel WITH rel "
144+
+ "MATCH (f:Person {id: $from}) "
145+
+ "MATCH (t:Language {name: rel.__target__.__id__}) "
146+
+ "CREATE (f)- [r:KNOWS {description: rel.__properties__.description}] -> (t) "
147+
+ "RETURN f, collect(r), collect(t)"
148+
)
111149
Person updateRel(@Param("from") String from, @Param("relations") List<Knows> relations);
150+
151+
// Using the whole person object
152+
@Query(""
153+
+ "UNWIND $person.__properties__.KNOWS As rel WITH rel "
154+
+ "MATCH (f:Person {id: $person.__id__}) "
155+
+ "MATCH (t:Language {name: rel.__target__.__id__}) "
156+
+ "CREATE (f) - [r:KNOWS {description: rel.__properties__.description}] -> (t) "
157+
+ "RETURN f, collect(r), collect(t)")
158+
Person updateRel2(@Param("person") Person person);
112159
}
113160

114161
@Service
@@ -127,6 +174,21 @@ public Person updateRel(String from, List<String> languageNames) {
127174
.collect(Collectors.toList());
128175
return personRepository.updateRel(from, knownLanguages);
129176
}
177+
178+
public Optional<Person> updateRel2(String id, List<String> languageNames) {
179+
180+
Optional<Person> original = personRepository.findById(id);
181+
if (original.isPresent()) {
182+
Person person = original.get();
183+
List<Knows> knownLanguages = languageNames.stream().map(Language::new)
184+
.map(language -> new Knows("Some description", language))
185+
.collect(Collectors.toList());
186+
person.setKnownLanguages(knownLanguages);
187+
return Optional.of(personRepository.updateRel2(person));
188+
}
189+
190+
return original;
191+
}
130192
}
131193

132194
@Configuration

src/test/java/org/springframework/data/neo4j/integration/issues/gh2323/Person.java

+4
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,8 @@ public List<Knows> getKnownLanguages() {
6161
public KnowsMtEntity getMotherTongue() {
6262
return motherTongue;
6363
}
64+
65+
public void setKnownLanguages(List<Knows> knownLanguages) {
66+
this.knownLanguages = knownLanguages;
67+
}
6468
}

src/test/java/org/springframework/data/neo4j/repository/query/Neo4jNestedMapEntityWriterTest.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -145,10 +145,10 @@ void relationshipPropertiesShouldBeSerializedWithTargetNodeWhenPassedFirstToWrit
145145
Map<String, Value> properties = (Map<String, Value>) result.get("__properties__");
146146
assertThat(properties).containsEntry("description", Values.value("Some description"));
147147

148-
assertThat(properties).hasEntrySatisfying("__target__", isAMapValue);
149-
properties = properties.get("__target__").asMap(Function.identity());
150-
assertThat(properties).containsEntry("__id__", Values.value("German"));
151-
assertThat(properties).hasEntrySatisfying("__properties__", isAMapValue);
148+
assertThat(result).hasEntrySatisfying("__target__", isAMapValue);
149+
Map<String, Value> target = ((Value) result.get("__target__")).asMap(Function.identity());
150+
assertThat(target).containsEntry("__id__", Values.value("German"));
151+
assertThat(target).hasEntrySatisfying("__properties__", isAMapValue);
152152
}
153153

154154
@Test // DATAGRAPH-1452

0 commit comments

Comments
 (0)