Skip to content

Commit 4938e6a

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 b18bdaf commit 4938e6a

File tree

4 files changed

+68
-12
lines changed

4 files changed

+68
-12
lines changed

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

+2-4
Original file line numberDiff line numberDiff line change
@@ -119,12 +119,10 @@ Map<String, Object> writeImpl(@Nullable Object source, Map<String, Object> sink,
119119
addLabels(sink, entity, propertyAccessor);
120120
addRelations(sink, entity, propertyAccessor, seenObjects);
121121
if (initialObject && entity.isRelationshipPropertiesEntity()) {
122-
@SuppressWarnings("unchecked")
123-
Map<String, Object> propertyMap = (Map<String, Object>) sink.get(Constants.NAME_OF_PROPERTIES_PARAM);
124122
entity.doWithProperties((PropertyHandler<Neo4jPersistentProperty>) p -> {
125123
if (p.isAnnotationPresent(TargetNode.class)) {
126-
Map<String, Object> target = this.writeImpl(propertyAccessor.getProperty(p), new HashMap<>(), seenObjects, false);
127-
propertyMap.put("__target__", Values.value(target));
124+
Value target = Values.value(this.writeImpl(propertyAccessor.getProperty(p), new HashMap<>(), seenObjects, false));
125+
sink.put("__target__", target);
128126
}
129127
});
130128
}

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

+58-4
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,11 @@
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;
26+
import org.junit.jupiter.api.BeforeEach;
2527
import org.junit.jupiter.api.Test;
2628
import org.neo4j.driver.Driver;
2729
import org.neo4j.driver.Session;
@@ -68,6 +70,17 @@ protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) {
6870
}
6971
}
7072

73+
@BeforeEach
74+
protected void removeRelationships(@Autowired BookmarkCapture bookmarkCapture) {
75+
try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig());
76+
Transaction transaction = session.beginTransaction();
77+
) {
78+
transaction.run("MATCH ()- [r:KNOWS]-() delete r").consume();
79+
transaction.commit();
80+
bookmarkCapture.seedWith(session.lastBookmark());
81+
}
82+
}
83+
7184
@Test // GH-2323
7285
void listOfRelationshipPropertiesShouldBeUnwindable(@Autowired PersonService personService) {
7386
Person person = personService.updateRel(personId, Arrays.asList("German"));
@@ -79,14 +92,40 @@ void listOfRelationshipPropertiesShouldBeUnwindable(@Autowired PersonService per
7992
});
8093
}
8194

95+
@Test // GH-2537
96+
void ensureRelationshipsAreSerialized(@Autowired PersonService personService) {
97+
98+
Optional<Person> optionalPerson = personService.updateRel2(personId, Arrays.asList("German"));
99+
assertThat(optionalPerson).isPresent().hasValueSatisfying(person -> {
100+
assertThat(person.getKnownLanguages()).hasSize(1);
101+
assertThat(person.getKnownLanguages()).first().satisfies(knows -> {
102+
assertThat(knows.getDescription()).isEqualTo("Some description");
103+
assertThat(knows.getLanguage()).extracting(Language::getName).isEqualTo("German");
104+
});
105+
});
106+
}
107+
82108
@Repository
83109
public interface PersonRepository extends Neo4jRepository<Person, String> {
84110

85-
@Query("UNWIND $relations As rel WITH rel " +
86-
"CREATE (f:Person {id: $from}) - [r:KNOWS {description: rel.__properties__.description}] -> (t:Language {name: rel.__properties__.__target__.__id__}) "
87-
+
88-
"RETURN f, collect(r), collect(t)")
111+
// Using separate id and than relationships on top level
112+
@Query(""
113+
+ "UNWIND $relations As rel WITH rel "
114+
+ "MATCH (f:Person {id: $from}) "
115+
+ "MATCH (t:Language {name: rel.__target__.__id__}) "
116+
+ "CREATE (f)- [r:KNOWS {description: rel.__properties__.description}] -> (t) "
117+
+ "RETURN f, collect(r), collect(t)"
118+
)
89119
Person updateRel(@Param("from") String from, @Param("relations") List<Knows> relations);
120+
121+
// Using the whole person object
122+
@Query(""
123+
+ "UNWIND $person.__properties__.KNOWS As rel WITH rel "
124+
+ "MATCH (f:Person {id: $person.__id__}) "
125+
+ "MATCH (t:Language {name: rel.__target__.__id__}) "
126+
+ "CREATE (f) - [r:KNOWS {description: rel.__properties__.description}] -> (t) "
127+
+ "RETURN f, collect(r), collect(t)")
128+
Person updateRel2(@Param("person") Person person);
90129
}
91130

92131
@Service
@@ -105,6 +144,21 @@ public Person updateRel(String from, List<String> languageNames) {
105144
.collect(Collectors.toList());
106145
return personRepository.updateRel(from, knownLanguages);
107146
}
147+
148+
public Optional<Person> updateRel2(String id, List<String> languageNames) {
149+
150+
Optional<Person> original = personRepository.findById(id);
151+
if (original.isPresent()) {
152+
Person person = original.get();
153+
List<Knows> knownLanguages = languageNames.stream().map(Language::new)
154+
.map(language -> new Knows("Some description", language))
155+
.collect(Collectors.toList());
156+
person.setKnownLanguages(knownLanguages);
157+
return Optional.of(personRepository.updateRel2(person));
158+
}
159+
160+
return original;
161+
}
108162
}
109163

110164
@Configuration

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

+4
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ public String getName() {
5252
public List<Knows> getKnownLanguages() {
5353
return knownLanguages;
5454
}
55+
56+
public void setKnownLanguages(List<Knows> knownLanguages) {
57+
this.knownLanguages = knownLanguages;
58+
}
5559
}

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

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

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

155155
@Test // DATAGRAPH-1452

0 commit comments

Comments
 (0)