GH-2819 - Improve parent type detection in result record.

Closes #2819
This commit is contained in:
Gerrit Meier
2023-11-20 16:57:40 +01:00
parent d2e67bc19d
commit b8bebe67ff
4 changed files with 195 additions and 20 deletions

View File

@@ -326,7 +326,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
knownObjects.removeFromInCreation(internalId);
populateProperties(queryResult, nodeDescription, internalId, instance, lastMappedEntity, relationshipsFromResult, nodesFromResult, false);
populateProperties(queryResult, (Neo4jPersistentEntity<ET>) genericTargetNodeDescription, nodeDescription, internalId, instance, lastMappedEntity, relationshipsFromResult, nodesFromResult, false);
PersistentPropertyAccessor<ET> propertyAccessor = concreteNodeDescription.getPropertyAccessor(getMostCurrentInstance(internalId, instance));
ET bean = propertyAccessor.getBean();
@@ -351,7 +351,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
// AND (!!!)
// 2. mutable target types
// because we cannot just create new instances
populateProperties(queryResult, nodeDescription, internalId, mappedObject, lastMappedEntity, relationshipsFromResult, nodesFromResult, true);
populateProperties(queryResult, (Neo4jPersistentEntity<ET>) genericTargetNodeDescription, nodeDescription, internalId, mappedObject, lastMappedEntity, relationshipsFromResult, nodesFromResult, true);
}
// due to a needed side effect in `populateProperties`, the entity might have been changed
return getMostCurrentInstance(internalId, mappedObject);
@@ -363,13 +363,13 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
}
private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEntity<ET> nodeDescription, String internalId,
private <ET> void populateProperties(MapAccessor queryResult, Neo4jPersistentEntity<ET> baseNodeDescription, Neo4jPersistentEntity<ET> moreConcreteNodeDescription, String internalId,
ET mappedObject, @Nullable Object lastMappedEntity,
Collection<Relationship> relationshipsFromResult, Collection<Node> nodesFromResult, boolean objectAlreadyMapped) {
List<String> allLabels = getLabels(queryResult, nodeDescription);
List<String> allLabels = getLabels(queryResult, moreConcreteNodeDescription);
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
.deriveConcreteNodeDescription(nodeDescription, allLabels);
.deriveConcreteNodeDescription(moreConcreteNodeDescription, allLabels);
@SuppressWarnings("unchecked")
Neo4jPersistentEntity<ET> concreteNodeDescription = (Neo4jPersistentEntity<ET>) nodeDescriptionAndLabels
@@ -397,7 +397,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
knownObjects.storeObject(internalId, propertyAccessor.getBean());
AssociationHandlerSupport.of(concreteNodeDescription).doWithAssociations(
populateFrom(queryResult, nodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
populateFrom(queryResult, baseNodeDescription, propertyAccessor, isConstructorParameter, objectAlreadyMapped, relationshipsFromResult, nodesFromResult));
}
@NonNull
@@ -469,12 +469,12 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
// If we cannot find any value it does not mean that there isn't any.
// The result set might contain associations not named CONCRETE_TYPE_TARGET but ABSTRACT_TYPE_TARGET.
// For this we bubble up the hierarchy of NodeDescriptions.
result = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, nodeDescription, genericNodeDescription, relationshipsFromResult, nodesFromResult)
result = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, genericNodeDescription, relationshipsFromResult, nodesFromResult)
.orElseGet(() -> {
NodeDescription<?> parentNodeDescription = nodeDescription.getParentNodeDescription();
T resultValue = null;
while (parentNodeDescription != null) {
Optional<Object> value = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, parentNodeDescription, parentNodeDescription, relationshipsFromResult, nodesFromResult);
Optional<Object> value = createInstanceOfRelationships(matchingProperty, values, relationshipDescription, parentNodeDescription, relationshipsFromResult, nodesFromResult);
if (value.isPresent()) {
resultValue = (T) value.get();
break;
@@ -567,7 +567,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
&& propertyValueNotNull;
if (populatedCollection) {
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, baseDescription, relationshipsFromResult, nodesFromResult, false)
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult, false)
.ifPresent(value -> {
Collection<?> providedCollection = (Collection<?>) value;
Collection<?> existingValue = (Collection<?>) propertyValue;
@@ -590,7 +590,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
return;
}
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, baseDescription, relationshipsFromResult, nodesFromResult)
createInstanceOfRelationships(persistentProperty, queryResult, (RelationshipDescription) association, baseDescription, relationshipsFromResult, nodesFromResult)
.ifPresent(value -> propertyAccessor.setProperty(persistentProperty, value));
};
}
@@ -614,13 +614,13 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
}
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, NodeDescription<?> genericNodeDescription, Collection<Relationship> relationshipsFromResult,
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
Collection<Node> nodesFromResult) {
return createInstanceOfRelationships(persistentProperty, values, relationshipDescription, baseDescription, genericNodeDescription, relationshipsFromResult, nodesFromResult, true);
return createInstanceOfRelationships(persistentProperty, values, relationshipDescription, baseDescription, relationshipsFromResult, nodesFromResult, true);
}
private Optional<Object> createInstanceOfRelationships(Neo4jPersistentProperty persistentProperty, MapAccessor values,
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, NodeDescription<?> genericNodeDescription, Collection<Relationship> relationshipsFromResult,
RelationshipDescription relationshipDescription, NodeDescription<?> baseDescription, Collection<Relationship> relationshipsFromResult,
Collection<Node> nodesFromResult, boolean fetchMore) {
String typeOfRelationship = relationshipDescription.getType();
@@ -654,8 +654,7 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
mappedObjectHandler = (type, mappedObject) -> value.add(mappedObject);
}
String collectionName = relationshipDescription.generateRelatedNodesCollectionName(genericNodeDescription);
String collectionName = relationshipDescription.generateRelatedNodesCollectionName(baseDescription);
Value list = values.get(collectionName);
boolean relationshipListEmptyOrNull = Values.NULL.equals(list);
@@ -715,12 +714,12 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
if (fetchMore) {
mappedObject = sourceNodeId != null && sourceNodeId.equals(targetNodeId)
? knownObjects.getObject("N" + sourceNodeId)
: map(possibleValueNode, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
: map(possibleValueNode, concreteTargetNodeDescription, baseDescription, null, null, relationshipsFromResult, nodesFromResult);
} else {
Object objectFromStore = knownObjects.getObject("N" + targetNodeId);
mappedObject = objectFromStore != null
? objectFromStore
: map(possibleValueNode, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
: map(possibleValueNode, concreteTargetNodeDescription, baseDescription, null, null, relationshipsFromResult, nodesFromResult);
}
if (relationshipDescription.hasRelationshipProperties()) {
@@ -752,16 +751,16 @@ final class DefaultNeo4jEntityConverter implements Neo4jEntityConverter {
Object valueEntry;
if (fetchMore) {
valueEntry = map(relatedEntity, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
valueEntry = map(relatedEntity, concreteTargetNodeDescription, genericTargetNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
} else {
Object objectFromStore = knownObjects.getObject(IdentitySupport.getPrefixedElementId(relatedEntity, null));
valueEntry = objectFromStore != null
? objectFromStore
: map(relatedEntity, concreteTargetNodeDescription, genericNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
: map(relatedEntity, concreteTargetNodeDescription, genericTargetNodeDescription, null, null, relationshipsFromResult, nodesFromResult);
}
if (relationshipDescription.hasRelationshipProperties()) {
String sourceLabel = relationshipDescription.getSource().getMostAbstractParentLabel(genericNodeDescription);
String sourceLabel = relationshipDescription.getSource().getMostAbstractParentLabel(baseDescription);
String relationshipSymbolicName = sourceLabel
+ RelationshipDescription.NAME_OF_RELATIONSHIP + targetLabel;
Relationship relatedEntityRelationship = relatedEntity.get(relationshipSymbolicName)

View File

@@ -143,6 +143,8 @@ import org.springframework.data.neo4j.integration.issues.gh2639.Individual;
import org.springframework.data.neo4j.integration.issues.gh2639.LanguageRelationship;
import org.springframework.data.neo4j.integration.issues.gh2639.ProgrammingLanguage;
import org.springframework.data.neo4j.integration.issues.gh2639.Sales;
import org.springframework.data.neo4j.integration.issues.gh2819.GH2819Model;
import org.springframework.data.neo4j.integration.issues.gh2819.GH2819Repository;
import org.springframework.data.neo4j.integration.issues.qbe.A;
import org.springframework.data.neo4j.integration.issues.qbe.ARepository;
import org.springframework.data.neo4j.integration.issues.qbe.B;
@@ -1103,6 +1105,25 @@ class IssuesIT extends TestBase {
});
}
@Test
@Tag("GH-2819")
void inheritanceAndProjectionShouldMapRelatedNodesCorrectly(@Autowired GH2819Repository repository, @Autowired Driver driver) {
try (var session = driver.session()) {
session.run("CREATE (a:ParentA:ChildA{name:'parentA', id:'a'})-[:HasBs]->(b:ParentB:ChildB{name:'parentB', id:'b'})-[:HasCs]->(c:ParentC:ChildC{name:'parentC', id:'c'})").consume();
}
var childAProjection = repository.findById("a", GH2819Model.ChildAProjection.class);
assertThat(childAProjection.getName()).isEqualTo("parentA");
var parentB = childAProjection.getParentB();
assertThat(parentB).isNotNull();
assertThat(parentB.getName()).isEqualTo("parentB");
var parentC = parentB.getParentC();
assertThat(parentC).isNotNull();
assertThat(parentC.getName()).isEqualTo("parentC");
}
@Configuration
@EnableTransactionManagement
@EnableNeo4jRepositories(namedQueriesLocation = "more-custom-queries.properties")

View File

@@ -0,0 +1,129 @@
/*
* Copyright 2011-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.integration.issues.gh2819;
import org.springframework.data.neo4j.core.schema.Id;
import org.springframework.data.neo4j.core.schema.Node;
import org.springframework.data.neo4j.core.schema.Relationship;
/**
* @author Gerrit Meier
*/
public class GH2819Model {
/**
* Projection of ParentA/ChildA
*/
public interface ChildAProjection {
String getName();
GH2819Model.ChildBProjection getParentB();
}
/**
* Projection of ParentB/ChildB
*/
public interface ChildBProjection {
String getName();
GH2819Model.ChildCProjection getParentC();
}
/**
* Projection of ParentC/ChildC
*/
public interface ChildCProjection {
String getName();
}
/**
* ParentA
*/
@Node
public static class ParentA {
@Id public String id;
@Relationship(type = "HasBs", direction = Relationship.Direction.OUTGOING)
public ParentB parentB;
public String name;
public String getName() {
return name;
}
public ParentB getParentB() {
return parentB;
}
}
/**
* ParentB
*/
@Node
public static class ParentB {
@Id public String id;
@Relationship(type = "HasCs", direction = Relationship.Direction.OUTGOING)
public ParentC parentC;
public String name;
public ParentC getParentC() {
return parentC;
}
public String getName() {
return name;
}
}
/**
* ParentC
*/
@Node
public static class ParentC {
@Id public String id;
public String name;
public String getName() {
return name;
}
}
/**
* ChildA
*/
@Node
public static class ChildA extends ParentA {
}
/**
* ChildB
*/
@Node
public static class ChildB extends ParentB {
}
/**
* ChildC
*/
@Node
public static class ChildC extends ParentC {
}
}

View File

@@ -0,0 +1,26 @@
/*
* Copyright 2011-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.neo4j.integration.issues.gh2819;
import org.springframework.data.neo4j.repository.Neo4jRepository;
/**
* @author Gerrit Meier
*/
public interface GH2819Repository extends Neo4jRepository<GH2819Model.ChildA, String> {
GH2819Model.ChildAProjection findById(String id, Class<?> projectionClass);
}