Add support for dynamic relationships with keys based on enums.
This change closes #219 by allowing Enums to be used as `Map`-Keys in dynamic relationships. Conversion from and to strings is done via the registered conversions.
This commit is contained in:
@@ -20,7 +20,6 @@ package org.neo4j.springframework.data.core;
|
||||
|
||||
import static java.util.Collections.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static org.neo4j.springframework.data.core.RelationshipStatementHolder.*;
|
||||
import static org.neo4j.opencypherdsl.Cypher.*;
|
||||
import static org.neo4j.springframework.data.core.schema.Constants.*;
|
||||
|
||||
@@ -461,17 +460,8 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
|
||||
Long relatedInternalId = saveRelatedNode(valueToBeSaved, relationshipContext.getAssociationTargetType(),
|
||||
targetNodeDescription, inDatabase);
|
||||
|
||||
// handle creation of relationship depending on properties on relationship or not
|
||||
RelationshipStatementHolder statementHolder = relationshipContext.hasRelationshipWithProperties()
|
||||
? createStatementForRelationShipWithProperties(neo4jMappingContext,
|
||||
neo4jPersistentEntity,
|
||||
relationshipContext,
|
||||
relatedInternalId,
|
||||
(Map.Entry) relatedValue)
|
||||
: createStatementForRelationshipWithoutProperties(neo4jPersistentEntity,
|
||||
relationshipContext,
|
||||
relatedInternalId,
|
||||
relatedValue);
|
||||
RelationshipStatementHolder statementHolder = RelationshipStatementHolder.createStatement(
|
||||
neo4jMappingContext, neo4jPersistentEntity, relationshipContext, relatedInternalId, relatedValue);
|
||||
|
||||
neo4jClient.query(renderer.render(statementHolder.getRelationshipCreationQuery()))
|
||||
.in(inDatabase)
|
||||
|
||||
@@ -21,7 +21,6 @@ package org.neo4j.springframework.data.core;
|
||||
import static java.util.Collections.*;
|
||||
import static java.util.stream.Collectors.*;
|
||||
import static org.neo4j.springframework.data.core.DatabaseSelection.*;
|
||||
import static org.neo4j.springframework.data.core.RelationshipStatementHolder.*;
|
||||
import static org.neo4j.opencypherdsl.Cypher.*;
|
||||
import static org.neo4j.springframework.data.core.schema.Constants.*;
|
||||
import static org.neo4j.springframework.data.core.support.Relationships.*;
|
||||
@@ -480,18 +479,8 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea
|
||||
relatedInternalId);
|
||||
}
|
||||
|
||||
// handle creation of relationship depending on properties on relationship or not
|
||||
RelationshipStatementHolder statementHolder = relationshipContext
|
||||
.hasRelationshipWithProperties()
|
||||
? createStatementForRelationShipWithProperties(neo4jMappingContext,
|
||||
neo4jPersistentEntity,
|
||||
relationshipContext,
|
||||
relatedInternalId,
|
||||
(Map.Entry) relatedValue)
|
||||
: createStatementForRelationshipWithoutProperties(neo4jPersistentEntity,
|
||||
relationshipContext,
|
||||
relatedInternalId,
|
||||
relatedValue);
|
||||
RelationshipStatementHolder statementHolder = RelationshipStatementHolder.createStatement(
|
||||
neo4jMappingContext, neo4jPersistentEntity, relationshipContext, relatedInternalId, relatedValue);
|
||||
|
||||
// in case of no properties the bind will just return an empty map
|
||||
Mono<ResultSummary> relationshipCreationMonoNested = neo4jClient
|
||||
|
||||
@@ -26,6 +26,7 @@ import org.neo4j.opencypherdsl.Statement;
|
||||
import org.neo4j.springframework.data.core.mapping.Neo4jMappingContext;
|
||||
import org.neo4j.springframework.data.core.mapping.Neo4jPersistentEntity;
|
||||
import org.neo4j.springframework.data.core.schema.CypherGenerator;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
||||
/**
|
||||
@@ -35,6 +36,7 @@ import org.springframework.lang.NonNull;
|
||||
* {@link Neo4jTemplate} as well as in the {@link ReactiveNeo4jTemplate}.
|
||||
*
|
||||
* @author Philipp Tölle
|
||||
* @author Michael J. Simons
|
||||
* @since 1.0
|
||||
*/
|
||||
final class RelationshipStatementHolder {
|
||||
@@ -42,13 +44,13 @@ final class RelationshipStatementHolder {
|
||||
private final Map<String, Object> properties;
|
||||
|
||||
private RelationshipStatementHolder(@NonNull Statement relationshipCreationQuery) {
|
||||
this.relationshipCreationQuery = relationshipCreationQuery;
|
||||
this.properties = Collections.emptyMap();
|
||||
this(relationshipCreationQuery, Collections.emptyMap());
|
||||
}
|
||||
|
||||
private RelationshipStatementHolder(
|
||||
@NonNull Statement relationshipCreationQuery,
|
||||
@NonNull Map<String, Object> properties) {
|
||||
@NonNull Map<String, Object> properties
|
||||
) {
|
||||
this.relationshipCreationQuery = relationshipCreationQuery;
|
||||
this.properties = properties;
|
||||
}
|
||||
@@ -61,7 +63,22 @@ final class RelationshipStatementHolder {
|
||||
return properties;
|
||||
}
|
||||
|
||||
static RelationshipStatementHolder createStatementForRelationShipWithProperties(
|
||||
static RelationshipStatementHolder createStatement(Neo4jMappingContext neo4jMappingContext,
|
||||
Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
NestedRelationshipContext relationshipContext,
|
||||
Long relatedInternalId,
|
||||
Object relatedValue) {
|
||||
|
||||
if (relationshipContext.hasRelationshipWithProperties()) {
|
||||
return createStatementForRelationShipWithProperties(neo4jMappingContext, neo4jPersistentEntity,
|
||||
relationshipContext, relatedInternalId, (Map.Entry) relatedValue);
|
||||
} else {
|
||||
return createStatementForRelationshipWithoutProperties(neo4jMappingContext, neo4jPersistentEntity,
|
||||
relationshipContext, relatedInternalId, relatedValue);
|
||||
}
|
||||
}
|
||||
|
||||
private static RelationshipStatementHolder createStatementForRelationShipWithProperties(
|
||||
Neo4jMappingContext neo4jMappingContext,
|
||||
Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
NestedRelationshipContext relationshipContext,
|
||||
@@ -80,16 +97,28 @@ final class RelationshipStatementHolder {
|
||||
return new RelationshipStatementHolder(relationshipCreationQuery, propMap);
|
||||
}
|
||||
|
||||
static RelationshipStatementHolder createStatementForRelationshipWithoutProperties(
|
||||
private static RelationshipStatementHolder createStatementForRelationshipWithoutProperties(
|
||||
Neo4jMappingContext neo4jMappingContext,
|
||||
Neo4jPersistentEntity<?> neo4jPersistentEntity,
|
||||
NestedRelationshipContext relationshipContext,
|
||||
Long relatedInternalId,
|
||||
Object relatedValue) {
|
||||
Object relatedValue
|
||||
) {
|
||||
|
||||
String relationshipType;
|
||||
if (!relationshipContext.getRelationship().isDynamic()) {
|
||||
relationshipType = null;
|
||||
} else {
|
||||
TypeInformation<?> keyType = relationshipContext.getInverse().getTypeInformation()
|
||||
.getRequiredComponentType();
|
||||
Object key = ((Map.Entry<?, ?>) relatedValue).getKey();
|
||||
relationshipType = neo4jMappingContext.getConverter().writeValueFromProperty(key, keyType).asString();
|
||||
}
|
||||
|
||||
Statement relationshipCreationQuery = CypherGenerator.INSTANCE
|
||||
.createRelationshipCreationQuery(neo4jPersistentEntity,
|
||||
relationshipContext.getRelationship(),
|
||||
relatedValue instanceof Map.Entry ? ((Map.Entry<String, ?>) relatedValue).getKey() : null,
|
||||
relationshipType,
|
||||
relatedInternalId);
|
||||
return new RelationshipStatementHolder(relationshipCreationQuery);
|
||||
}
|
||||
|
||||
@@ -263,12 +263,15 @@ final class DefaultNeo4jConverter implements Neo4jConverter {
|
||||
KnownObjects knownObjects) {
|
||||
|
||||
List<String> allLabels = getLabels(queryResult);
|
||||
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore.deriveConcreteNodeDescription(nodeDescription, allLabels);
|
||||
Neo4jPersistentEntity<ET> concreteNodeDescription = (Neo4jPersistentEntity<ET>) nodeDescriptionAndLabels.getNodeDescription();
|
||||
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
|
||||
.deriveConcreteNodeDescription(nodeDescription, allLabels);
|
||||
Neo4jPersistentEntity<ET> concreteNodeDescription = (Neo4jPersistentEntity<ET>) nodeDescriptionAndLabels
|
||||
.getNodeDescription();
|
||||
|
||||
Collection<RelationshipDescription> relationships = concreteNodeDescription.getRelationships();
|
||||
|
||||
ET instance = instantiate(concreteNodeDescription, queryResult, knownObjects, relationships, nodeDescriptionAndLabels.getDynamicLabels());
|
||||
ET instance = instantiate(concreteNodeDescription, queryResult, knownObjects, relationships,
|
||||
nodeDescriptionAndLabels.getDynamicLabels());
|
||||
|
||||
PersistentPropertyAccessor<ET> propertyAccessor = concreteNodeDescription.getPropertyAccessor(instance);
|
||||
|
||||
@@ -321,7 +324,8 @@ final class DefaultNeo4jConverter implements Neo4jConverter {
|
||||
.getRequiredPersistentProperty(parameter.getName());
|
||||
|
||||
if (matchingProperty.isRelationship()) {
|
||||
return createInstanceOfRelationships(matchingProperty, values, knownObjects, relationships).orElse(null);
|
||||
return createInstanceOfRelationships(matchingProperty, values, knownObjects, relationships)
|
||||
.orElse(null);
|
||||
} else if (matchingProperty.isDynamicLabels()) {
|
||||
return createDynamicLabelsProperty(matchingProperty.getTypeInformation(), surplusLabels);
|
||||
}
|
||||
@@ -329,7 +333,8 @@ final class DefaultNeo4jConverter implements Neo4jConverter {
|
||||
}
|
||||
};
|
||||
|
||||
return INSTANTIATORS.getInstantiatorFor(nodeDescription).createInstance(nodeDescription, parameterValueProvider);
|
||||
return INSTANTIATORS.getInstantiatorFor(nodeDescription)
|
||||
.createInstance(nodeDescription, parameterValueProvider);
|
||||
}
|
||||
|
||||
private PropertyHandler<Neo4jPersistentProperty> populateFrom(
|
||||
@@ -344,7 +349,8 @@ final class DefaultNeo4jConverter implements Neo4jConverter {
|
||||
}
|
||||
|
||||
if (property.isDynamicLabels()) {
|
||||
propertyAccessor.setProperty(property, createDynamicLabelsProperty(property.getTypeInformation(), surplusLabels));
|
||||
propertyAccessor
|
||||
.setProperty(property, createDynamicLabelsProperty(property.getTypeInformation(), surplusLabels));
|
||||
} else {
|
||||
propertyAccessor.setProperty(property,
|
||||
readValueForProperty(extractValueOf(property, queryResult), property.getTypeInformation()));
|
||||
@@ -387,24 +393,32 @@ final class DefaultNeo4jConverter implements Neo4jConverter {
|
||||
(Neo4jPersistentEntity<?>) relationshipDescription.getTarget();
|
||||
|
||||
List<String> allLabels = getLabels(values);
|
||||
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore.deriveConcreteNodeDescription(genericTargetNodeDescription, allLabels);
|
||||
Neo4jPersistentEntity<?> concreteTargetNodeDescription = (Neo4jPersistentEntity<?>) nodeDescriptionAndLabels.getNodeDescription();
|
||||
NodeDescriptionAndLabels nodeDescriptionAndLabels = nodeDescriptionStore
|
||||
.deriveConcreteNodeDescription(genericTargetNodeDescription, allLabels);
|
||||
Neo4jPersistentEntity<?> concreteTargetNodeDescription = (Neo4jPersistentEntity<?>) nodeDescriptionAndLabels
|
||||
.getNodeDescription();
|
||||
|
||||
List<Object> value = new ArrayList<>();
|
||||
Map<String, Object> dynamicValue = new HashMap<>();
|
||||
Map<Object, Object> dynamicValue = new HashMap<>();
|
||||
|
||||
BiConsumer<String, Object> mappedObjectHandler;
|
||||
Function<String, ?> keyTransformer;
|
||||
if (persistentProperty.isDynamicAssociation() && persistentProperty.getComponentType().isEnum()) {
|
||||
keyTransformer = f -> conversionService.convert(f, persistentProperty.getComponentType());
|
||||
} else {
|
||||
keyTransformer = Function.identity();
|
||||
}
|
||||
if (persistentProperty.isDynamicOneToManyAssociation()) {
|
||||
|
||||
TypeInformation<?> actualType = persistentProperty.getTypeInformation().getRequiredActualType();
|
||||
mappedObjectHandler = (type, mappedObject) -> {
|
||||
List<Object> bucket = (List<Object>) dynamicValue.computeIfAbsent(type,
|
||||
List<Object> bucket = (List<Object>) dynamicValue.computeIfAbsent(keyTransformer.apply(type),
|
||||
s -> createCollection(actualType.getType(), persistentProperty.getAssociationTargetType(),
|
||||
values.size()));
|
||||
bucket.add(mappedObject);
|
||||
};
|
||||
} else if (persistentProperty.isDynamicAssociation()) {
|
||||
mappedObjectHandler = dynamicValue::put;
|
||||
mappedObjectHandler = (type, mappedObject) -> dynamicValue.put(keyTransformer.apply(type), mappedObject);
|
||||
} else {
|
||||
mappedObjectHandler = (type, mappedObject) -> value.add(mappedObject);
|
||||
}
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
package org.neo4j.springframework.data.core.mapping;
|
||||
|
||||
import org.apiguardian.api.API;
|
||||
import org.neo4j.springframework.data.core.schema.DynamicLabels;
|
||||
import org.neo4j.springframework.data.core.schema.GraphPropertyDescription;
|
||||
import org.neo4j.springframework.data.core.schema.RelationshipProperties;
|
||||
import org.neo4j.springframework.data.core.schema.DynamicLabels;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
|
||||
/**
|
||||
@@ -36,12 +36,13 @@ public interface Neo4jPersistentProperty
|
||||
extends PersistentProperty<Neo4jPersistentProperty>, GraphPropertyDescription {
|
||||
|
||||
/**
|
||||
* Dynamic associations are associations to non-simple types stored in a map with a key type of {@literal java.lang.String}.
|
||||
* Dynamic associations are associations to non-simple types stored in a map
|
||||
* with a key type of {@literal java.lang.String} or enum.
|
||||
*
|
||||
* @return True, if this association is a dynamic association.
|
||||
*/
|
||||
default boolean isDynamicAssociation() {
|
||||
return isAssociation() && isMap() && getComponentType() == String.class;
|
||||
return isAssociation() && isMap() && (getComponentType() == String.class || getComponentType().isEnum());
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -114,7 +114,8 @@ class Neo4jMappingContextTest {
|
||||
schema.setInitialEntitySet(new HashSet<>(Arrays.asList(InvalidId.class)));
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> schema.initialize())
|
||||
.withMessageMatching("Cannot use internal id strategy with custom property getMappingFunctionFor on entity .*");
|
||||
.withMessageMatching(
|
||||
"Cannot use internal id strategy with custom property getMappingFunctionFor on entity .*");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -142,7 +143,8 @@ class Neo4jMappingContextTest {
|
||||
Neo4jMappingContext schema = new Neo4jMappingContext();
|
||||
Neo4jPersistentEntity<?> bikeNodeEntity = schema.getPersistentEntity(BikeNode.class);
|
||||
bikeNodeEntity.doWithAssociations((Association<Neo4jPersistentProperty> association) ->
|
||||
assertThat(schema.getRequiredMappingFunctionFor(association.getInverse().getAssociationTargetType())).isNotNull());
|
||||
assertThat(schema.getRequiredMappingFunctionFor(association.getInverse().getAssociationTargetType()))
|
||||
.isNotNull());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -186,7 +188,8 @@ class Neo4jMappingContextTest {
|
||||
}
|
||||
}
|
||||
|
||||
Neo4jMappingContext schema = new Neo4jMappingContext(new Neo4jConversions(singleton(new ConvertibleTypeConverter())));
|
||||
Neo4jMappingContext schema = new Neo4jMappingContext(
|
||||
new Neo4jConversions(singleton(new ConvertibleTypeConverter())));
|
||||
Neo4jPersistentEntity<?> entity = schema.getPersistentEntity(EntityWithConvertibleProperty.class);
|
||||
|
||||
assertThat(entity.getPersistentProperty("convertibleType").isRelationship()).isFalse();
|
||||
@@ -217,6 +220,19 @@ class Neo4jMappingContextTest {
|
||||
assertThat(associations).containsOnly("bikes");
|
||||
}
|
||||
|
||||
@Test
|
||||
void enumMapKeys() {
|
||||
|
||||
Neo4jMappingContext schema = new Neo4jMappingContext();
|
||||
Neo4jPersistentEntity<?> enumRelNodeEntity = schema.getPersistentEntity(EnumRelNode.class);
|
||||
|
||||
List<Neo4jPersistentProperty> associations = new ArrayList<>();
|
||||
enumRelNodeEntity
|
||||
.doWithAssociations((Association<Neo4jPersistentProperty> a) -> associations.add(a.getInverse()));
|
||||
|
||||
assertThat(associations).hasSize(2);
|
||||
}
|
||||
|
||||
static class DummyIdGenerator implements IdGenerator<Void> {
|
||||
|
||||
@Override
|
||||
@@ -250,6 +266,23 @@ class Neo4jMappingContextTest {
|
||||
|
||||
}
|
||||
|
||||
enum A {
|
||||
A1, A2
|
||||
}
|
||||
|
||||
enum ExtendedA {
|
||||
|
||||
EA1, EA2 {
|
||||
@Override
|
||||
public void doNothing() {
|
||||
}
|
||||
};
|
||||
|
||||
public void doNothing() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
static class BikeNode {
|
||||
|
||||
@Id
|
||||
@@ -267,6 +300,16 @@ class Neo4jMappingContextTest {
|
||||
Map<String, Object> funnyDynamicProperties;
|
||||
}
|
||||
|
||||
static class EnumRelNode {
|
||||
|
||||
@Id
|
||||
private String id;
|
||||
|
||||
Map<A, UserNode> relA;
|
||||
|
||||
Map<ExtendedA, BikeNode> relEA;
|
||||
}
|
||||
|
||||
static class TripNode {
|
||||
|
||||
@Id
|
||||
|
||||
@@ -34,6 +34,8 @@ import org.neo4j.springframework.data.config.AbstractNeo4jConfig;
|
||||
import org.neo4j.springframework.data.integration.shared.DynamicRelationshipsITBase;
|
||||
import org.neo4j.springframework.data.integration.shared.Person;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithRelatives;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithRelatives.TypeOfPet;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithRelatives.TypeOfRelative;
|
||||
import org.neo4j.springframework.data.integration.shared.Pet;
|
||||
import org.neo4j.springframework.data.repository.config.EnableNeo4jRepositories;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
@@ -44,13 +46,11 @@ import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
class DynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
class DynamicRelationshipsIT extends DynamicRelationshipsITBase<PersonWithRelatives> {
|
||||
|
||||
@Autowired
|
||||
DynamicRelationshipsIT(Driver driver) {
|
||||
@Autowired DynamicRelationshipsIT(Driver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
@@ -61,10 +61,10 @@ class DynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
assertThat(relatives.get("HAS_WIFE").getFirstName()).isEqualTo("B");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C");
|
||||
Map<TypeOfRelative, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys(TypeOfRelative.HAS_WIFE, TypeOfRelative.HAS_DAUGHTER);
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_WIFE).getFirstName()).isEqualTo("B");
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_DAUGHTER).getFirstName()).isEqualTo("C");
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
@@ -74,10 +74,10 @@ class DynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield");
|
||||
assertThat(pets.get("DOGS")).extracting(Pet::getName).containsExactlyInAnyOrder("Benji", "Lassie");
|
||||
Map<TypeOfPet, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys(TypeOfPet.CATS, TypeOfPet.DOGS);
|
||||
assertThat(pets.get(TypeOfPet.CATS)).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield");
|
||||
assertThat(pets.get(TypeOfPet.DOGS)).extracting(Pet::getName).containsExactlyInAnyOrder("Benji", "Lassie");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -87,20 +87,20 @@ class DynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assumeThat(person).isNotNull();
|
||||
assumeThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assumeThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
Map<TypeOfRelative, Person> relatives = person.getRelatives();
|
||||
assumeThat(relatives).containsOnlyKeys(TypeOfRelative.HAS_WIFE, TypeOfRelative.HAS_DAUGHTER);
|
||||
|
||||
relatives.remove("HAS_WIFE");
|
||||
relatives.remove(TypeOfRelative.HAS_WIFE);
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "D");
|
||||
relatives.put("HAS_SON", d);
|
||||
ReflectionTestUtils.setField(relatives.get("HAS_DAUGHTER"), "firstName", "C2");
|
||||
relatives.put(TypeOfRelative.HAS_SON, d);
|
||||
ReflectionTestUtils.setField(relatives.get(TypeOfRelative.HAS_DAUGHTER), "firstName", "C2");
|
||||
|
||||
person = repository.save(person);
|
||||
relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_DAUGHTER", "HAS_SON");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C2");
|
||||
assertThat(relatives.get("HAS_SON").getFirstName()).isEqualTo("D");
|
||||
assertThat(relatives).containsOnlyKeys(TypeOfRelative.HAS_DAUGHTER, TypeOfRelative.HAS_SON);
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_DAUGHTER).getFirstName()).isEqualTo("C2");
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_SON).getFirstName()).isEqualTo("D");
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
@@ -110,42 +110,43 @@ class DynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
Map<TypeOfPet, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys(TypeOfPet.CATS, TypeOfPet.DOGS);
|
||||
|
||||
pets.remove("DOGS");
|
||||
pets.get("CATS").add(new Pet("Delilah"));
|
||||
pets.remove(TypeOfPet.DOGS);
|
||||
pets.get(TypeOfPet.CATS).add(new Pet("Delilah"));
|
||||
|
||||
pets.put("FISH", Collections.singletonList(new Pet("Nemo")));
|
||||
pets.put(TypeOfPet.FISH, Collections.singletonList(new Pet("Nemo")));
|
||||
|
||||
person = repository.save(person);
|
||||
pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "FISH");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield", "Delilah");
|
||||
assertThat(pets.get("FISH")).extracting(Pet::getName).containsExactlyInAnyOrder("Nemo");
|
||||
assertThat(pets).containsOnlyKeys(TypeOfPet.CATS, TypeOfPet.FISH);
|
||||
assertThat(pets.get(TypeOfPet.CATS)).extracting(Pet::getName)
|
||||
.containsExactlyInAnyOrder("Tom", "Garfield", "Delilah");
|
||||
assertThat(pets.get(TypeOfPet.FISH)).extracting(Pet::getName).containsExactlyInAnyOrder("Nemo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteDynamicRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithRelatives newPerson = new PersonWithRelatives("Test");
|
||||
Map<String, Person> relatives = newPerson.getRelatives();
|
||||
Map<TypeOfRelative, Person> relatives = newPerson.getRelatives();
|
||||
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R1");
|
||||
relatives.put("RELATIVE_1", d);
|
||||
relatives.put(TypeOfRelative.RELATIVE_1, d);
|
||||
|
||||
d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R2");
|
||||
relatives.put("RELATIVE_2", d);
|
||||
relatives.put(TypeOfRelative.RELATIVE_2, d);
|
||||
|
||||
newPerson = repository.save(newPerson);
|
||||
relatives = newPerson.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("RELATIVE_1", "RELATIVE_2");
|
||||
assertThat(relatives).containsOnlyKeys(TypeOfRelative.RELATIVE_1, TypeOfRelative.RELATIVE_2);
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:PersonWithRelatives) WHERE id(t) = $id "
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Person))"
|
||||
+ " as numberOfRelations", Values.parameters("id", newPerson.getId()))
|
||||
.single().get("numberOfRelations").asLong();
|
||||
@@ -157,22 +158,22 @@ class DynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
void shouldWriteDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithRelatives newPerson = new PersonWithRelatives("Test");
|
||||
Map<String, List<Pet>> pets = newPerson.getPets();
|
||||
Map<TypeOfPet, List<Pet>> pets = newPerson.getPets();
|
||||
|
||||
List<Pet> monsters = pets.computeIfAbsent("MONSTERS", s -> new ArrayList<>());
|
||||
List<Pet> monsters = pets.computeIfAbsent(TypeOfPet.MONSTERS, s -> new ArrayList<>());
|
||||
monsters.add(new Pet("Godzilla"));
|
||||
monsters.add(new Pet("King Kong"));
|
||||
|
||||
List<Pet> fish = pets.computeIfAbsent("FISH", s -> new ArrayList<>());
|
||||
List<Pet> fish = pets.computeIfAbsent(TypeOfPet.FISH, s -> new ArrayList<>());
|
||||
fish.add(new Pet("Nemo"));
|
||||
|
||||
newPerson = repository.save(newPerson);
|
||||
pets = newPerson.getPets();
|
||||
assertThat(pets).containsOnlyKeys("MONSTERS", "FISH");
|
||||
assertThat(pets).containsOnlyKeys(TypeOfPet.MONSTERS, TypeOfPet.FISH);
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:PersonWithRelatives) WHERE id(t) = $id "
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Pet))"
|
||||
+ " as numberOfRelations", Values.parameters("id", newPerson.getId()))
|
||||
.single().get("numberOfRelations").asLong();
|
||||
@@ -192,6 +193,5 @@ class DynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
public Driver driver() {
|
||||
return neo4jConnectionSupport.getDriver();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 "Neo4j,"
|
||||
* Neo4j Sweden AB [https://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
*
|
||||
* 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.neo4j.springframework.data.integration.imperative;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Transaction;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.springframework.data.config.AbstractNeo4jConfig;
|
||||
import org.neo4j.springframework.data.integration.shared.DynamicRelationshipsITBase;
|
||||
import org.neo4j.springframework.data.integration.shared.Person;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithStringlyTypedRelatives;
|
||||
import org.neo4j.springframework.data.integration.shared.Pet;
|
||||
import org.neo4j.springframework.data.repository.config.EnableNeo4jRepositories;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.repository.CrudRepository;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
class StringlyTypedDynamicRelationshipsIT extends DynamicRelationshipsITBase<PersonWithStringlyTypedRelatives> {
|
||||
|
||||
@Autowired
|
||||
StringlyTypedDynamicRelationshipsIT(Driver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadDynamicRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives person = repository.findById(idOfExistingPerson).get();
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
assertThat(relatives.get("HAS_WIFE").getFirstName()).isEqualTo("B");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C");
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
void shouldReadDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives person = repository.findById(idOfExistingPerson).get();
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield");
|
||||
assertThat(pets.get("DOGS")).extracting(Pet::getName).containsExactlyInAnyOrder("Benji", "Lassie");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateDynamicRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives person = repository.findById(idOfExistingPerson).get();
|
||||
assumeThat(person).isNotNull();
|
||||
assumeThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assumeThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
|
||||
relatives.remove("HAS_WIFE");
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "D");
|
||||
relatives.put("HAS_SON", d);
|
||||
ReflectionTestUtils.setField(relatives.get("HAS_DAUGHTER"), "firstName", "C2");
|
||||
|
||||
person = repository.save(person);
|
||||
relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_DAUGHTER", "HAS_SON");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C2");
|
||||
assertThat(relatives.get("HAS_SON").getFirstName()).isEqualTo("D");
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
void shouldUpdateDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives person = repository.findById(idOfExistingPerson).get();
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
|
||||
pets.remove("DOGS");
|
||||
pets.get("CATS").add(new Pet("Delilah"));
|
||||
|
||||
pets.put("FISH", Collections.singletonList(new Pet("Nemo")));
|
||||
|
||||
person = repository.save(person);
|
||||
pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "FISH");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield", "Delilah");
|
||||
assertThat(pets.get("FISH")).extracting(Pet::getName).containsExactlyInAnyOrder("Nemo");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteDynamicRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives newPerson = new PersonWithStringlyTypedRelatives("Test");
|
||||
Map<String, Person> relatives = newPerson.getRelatives();
|
||||
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R1");
|
||||
relatives.put("RELATIVE_1", d);
|
||||
|
||||
d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R2");
|
||||
relatives.put("RELATIVE_2", d);
|
||||
|
||||
newPerson = repository.save(newPerson);
|
||||
relatives = newPerson.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("RELATIVE_1", "RELATIVE_2");
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Person))"
|
||||
+ " as numberOfRelations", Values.parameters("id", newPerson.getId()))
|
||||
.single().get("numberOfRelations").asLong();
|
||||
assertThat(numberOfRelations).isEqualTo(2L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
void shouldWriteDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives newPerson = new PersonWithStringlyTypedRelatives("Test");
|
||||
Map<String, List<Pet>> pets = newPerson.getPets();
|
||||
|
||||
List<Pet> monsters = pets.computeIfAbsent("MONSTERS", s -> new ArrayList<>());
|
||||
monsters.add(new Pet("Godzilla"));
|
||||
monsters.add(new Pet("King Kong"));
|
||||
|
||||
List<Pet> fish = pets.computeIfAbsent("FISH", s -> new ArrayList<>());
|
||||
fish.add(new Pet("Nemo"));
|
||||
|
||||
newPerson = repository.save(newPerson);
|
||||
pets = newPerson.getPets();
|
||||
assertThat(pets).containsOnlyKeys("MONSTERS", "FISH");
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Pet))"
|
||||
+ " as numberOfRelations", Values.parameters("id", newPerson.getId()))
|
||||
.single().get("numberOfRelations").asLong();
|
||||
assertThat(numberOfRelations).isEqualTo(3L);
|
||||
}
|
||||
}
|
||||
|
||||
interface PersonWithRelativesRepository extends CrudRepository<PersonWithStringlyTypedRelatives, Long> {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@EnableNeo4jRepositories(considerNestedRepositories = true)
|
||||
static class Config extends AbstractNeo4jConfig {
|
||||
|
||||
@Bean
|
||||
public Driver driver() {
|
||||
return neo4jConnectionSupport.getDriver();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,8 @@ import org.neo4j.springframework.data.config.AbstractReactiveNeo4jConfig;
|
||||
import org.neo4j.springframework.data.integration.shared.DynamicRelationshipsITBase;
|
||||
import org.neo4j.springframework.data.integration.shared.Person;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithRelatives;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithRelatives.TypeOfPet;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithRelatives.TypeOfRelative;
|
||||
import org.neo4j.springframework.data.integration.shared.Pet;
|
||||
import org.neo4j.springframework.data.repository.ReactiveNeo4jRepository;
|
||||
import org.neo4j.springframework.data.repository.config.EnableReactiveNeo4jRepositories;
|
||||
@@ -51,7 +53,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Tag(NEEDS_REACTIVE_SUPPORT)
|
||||
class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase<PersonWithRelatives> {
|
||||
|
||||
@Autowired ReactiveDynamicRelationshipsIT(Driver driver) {
|
||||
super(driver);
|
||||
@@ -66,10 +68,10 @@ class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
assertThat(relatives.get("HAS_WIFE").getFirstName()).isEqualTo("B");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C");
|
||||
Map<TypeOfRelative, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys(TypeOfRelative.HAS_WIFE, TypeOfRelative.HAS_DAUGHTER);
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_WIFE).getFirstName()).isEqualTo("B");
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_DAUGHTER).getFirstName()).isEqualTo("C");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -83,10 +85,10 @@ class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield");
|
||||
assertThat(pets.get("DOGS")).extracting(Pet::getName).containsExactlyInAnyOrder("Benji", "Lassie");
|
||||
Map<TypeOfPet, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys(TypeOfPet.CATS, TypeOfPet.DOGS);
|
||||
assertThat(pets.get(TypeOfPet.CATS)).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield");
|
||||
assertThat(pets.get(TypeOfPet.DOGS)).extracting(Pet::getName).containsExactlyInAnyOrder("Benji", "Lassie");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -99,23 +101,23 @@ class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assumeThat(person).isNotNull();
|
||||
assumeThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assumeThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
Map<TypeOfRelative, Person> relatives = person.getRelatives();
|
||||
assumeThat(relatives).containsOnlyKeys(TypeOfRelative.HAS_WIFE, TypeOfRelative.HAS_DAUGHTER);
|
||||
|
||||
relatives.remove("HAS_WIFE");
|
||||
relatives.remove(TypeOfRelative.HAS_WIFE);
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "D");
|
||||
relatives.put("HAS_SON", d);
|
||||
ReflectionTestUtils.setField(relatives.get("HAS_DAUGHTER"), "firstName", "C2");
|
||||
relatives.put(TypeOfRelative.HAS_SON, d);
|
||||
ReflectionTestUtils.setField(relatives.get(TypeOfRelative.HAS_DAUGHTER), "firstName", "C2");
|
||||
return person;
|
||||
})
|
||||
.flatMap(repository::save)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(person -> {
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_DAUGHTER", "HAS_SON");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C2");
|
||||
assertThat(relatives.get("HAS_SON").getFirstName()).isEqualTo("D");
|
||||
Map<TypeOfRelative, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys(TypeOfRelative.HAS_DAUGHTER, TypeOfRelative.HAS_SON);
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_DAUGHTER).getFirstName()).isEqualTo("C2");
|
||||
assertThat(relatives.get(TypeOfRelative.HAS_SON).getFirstName()).isEqualTo("D");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -128,23 +130,23 @@ class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
assumeThat(person).isNotNull();
|
||||
assumeThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
Map<TypeOfPet, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys(TypeOfPet.CATS, TypeOfPet.DOGS);
|
||||
|
||||
pets.remove("DOGS");
|
||||
pets.get("CATS").add(new Pet("Delilah"));
|
||||
pets.remove(TypeOfPet.DOGS);
|
||||
pets.get(TypeOfPet.CATS).add(new Pet("Delilah"));
|
||||
|
||||
pets.put("FISH", Collections.singletonList(new Pet("Nemo")));
|
||||
pets.put(TypeOfPet.FISH, Collections.singletonList(new Pet("Nemo")));
|
||||
|
||||
return person;
|
||||
})
|
||||
.flatMap(repository::save)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(person -> {
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "FISH");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield", "Delilah");
|
||||
assertThat(pets.get("FISH")).extracting(Pet::getName).containsExactlyInAnyOrder("Nemo");
|
||||
Map<TypeOfPet, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys(TypeOfPet.CATS, TypeOfPet.FISH);
|
||||
assertThat(pets.get(TypeOfPet.CATS)).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield", "Delilah");
|
||||
assertThat(pets.get(TypeOfPet.FISH)).extracting(Pet::getName).containsExactlyInAnyOrder("Nemo");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
@@ -155,24 +157,24 @@ class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
PersonWithRelatives newPerson = new PersonWithRelatives("Test");
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R1");
|
||||
newPerson.getRelatives().put("RELATIVE_1", d);
|
||||
newPerson.getRelatives().put(TypeOfRelative.RELATIVE_1, d);
|
||||
d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R2");
|
||||
newPerson.getRelatives().put("RELATIVE_2", d);
|
||||
newPerson.getRelatives().put(TypeOfRelative.RELATIVE_2, d);
|
||||
|
||||
List<PersonWithRelatives> recorded = new ArrayList<>();
|
||||
repository.save(newPerson)
|
||||
.as(StepVerifier::create)
|
||||
.recordWith(() -> recorded)
|
||||
.consumeNextWith(personWithRelatives -> {
|
||||
Map<String, Person> relatives = personWithRelatives.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("RELATIVE_1", "RELATIVE_2");
|
||||
Map<TypeOfRelative, Person> relatives = personWithRelatives.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys(TypeOfRelative.RELATIVE_1, TypeOfRelative.RELATIVE_2);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:PersonWithRelatives) WHERE id(t) = $id "
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Person))"
|
||||
+ " as numberOfRelations",
|
||||
Values.parameters("id", recorded.get(0).getId()))
|
||||
@@ -185,13 +187,13 @@ class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
void shouldWriteDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithRelatives newPerson = new PersonWithRelatives("Test");
|
||||
Map<String, List<Pet>> pets = newPerson.getPets();
|
||||
Map<TypeOfPet, List<Pet>> pets = newPerson.getPets();
|
||||
|
||||
List<Pet> monsters = pets.computeIfAbsent("MONSTERS", s -> new ArrayList<>());
|
||||
List<Pet> monsters = pets.computeIfAbsent(TypeOfPet.MONSTERS, s -> new ArrayList<>());
|
||||
monsters.add(new Pet("Godzilla"));
|
||||
monsters.add(new Pet("King Kong"));
|
||||
|
||||
List<Pet> fish = pets.computeIfAbsent("FISH", s -> new ArrayList<>());
|
||||
List<Pet> fish = pets.computeIfAbsent(TypeOfPet.FISH, s -> new ArrayList<>());
|
||||
fish.add(new Pet("Nemo"));
|
||||
|
||||
List<PersonWithRelatives> recorded = new ArrayList<>();
|
||||
@@ -199,14 +201,14 @@ class ReactiveDynamicRelationshipsIT extends DynamicRelationshipsITBase {
|
||||
.as(StepVerifier::create)
|
||||
.recordWith(() -> recorded)
|
||||
.consumeNextWith(person -> {
|
||||
Map<String, List<Pet>> writtenPets = person.getPets();
|
||||
assertThat(writtenPets).containsOnlyKeys("MONSTERS", "FISH");
|
||||
Map<TypeOfPet, List<Pet>> writtenPets = person.getPets();
|
||||
assertThat(writtenPets).containsOnlyKeys(TypeOfPet.MONSTERS, TypeOfPet.FISH);
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:PersonWithRelatives) WHERE id(t) = $id "
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Pet))"
|
||||
+ " as numberOfRelations", Values.parameters("id", recorded.get(0).getId()))
|
||||
.single().get("numberOfRelations").asLong();
|
||||
|
||||
@@ -0,0 +1,231 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 "Neo4j,"
|
||||
* Neo4j Sweden AB [https://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
*
|
||||
* 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.neo4j.springframework.data.integration.reactive;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.assertj.core.api.Assumptions.*;
|
||||
import static org.neo4j.springframework.data.test.Neo4jExtension.*;
|
||||
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Tag;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Transaction;
|
||||
import org.neo4j.driver.Values;
|
||||
import org.neo4j.springframework.data.config.AbstractReactiveNeo4jConfig;
|
||||
import org.neo4j.springframework.data.integration.shared.DynamicRelationshipsITBase;
|
||||
import org.neo4j.springframework.data.integration.shared.Person;
|
||||
import org.neo4j.springframework.data.integration.shared.PersonWithStringlyTypedRelatives;
|
||||
import org.neo4j.springframework.data.integration.shared.Pet;
|
||||
import org.neo4j.springframework.data.repository.ReactiveNeo4jRepository;
|
||||
import org.neo4j.springframework.data.repository.config.EnableReactiveNeo4jRepositories;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Tag(NEEDS_REACTIVE_SUPPORT)
|
||||
class ReactiveStringlyTypeDynamicRelationshipsIT extends DynamicRelationshipsITBase<PersonWithStringlyTypedRelatives> {
|
||||
|
||||
@Autowired ReactiveStringlyTypeDynamicRelationshipsIT(Driver driver) {
|
||||
super(driver);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReadDynamicRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
repository.findById(idOfExistingPerson)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(person -> {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
assertThat(relatives.get("HAS_WIFE").getFirstName()).isEqualTo("B");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
void shouldReadDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
repository.findById(idOfExistingPerson)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(person -> {
|
||||
assertThat(person).isNotNull();
|
||||
assertThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield");
|
||||
assertThat(pets.get("DOGS")).extracting(Pet::getName).containsExactlyInAnyOrder("Benji", "Lassie");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateDynamicRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
repository.findById(idOfExistingPerson)
|
||||
.map(person -> {
|
||||
assumeThat(person).isNotNull();
|
||||
assumeThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assumeThat(relatives).containsOnlyKeys("HAS_WIFE", "HAS_DAUGHTER");
|
||||
|
||||
relatives.remove("HAS_WIFE");
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "D");
|
||||
relatives.put("HAS_SON", d);
|
||||
ReflectionTestUtils.setField(relatives.get("HAS_DAUGHTER"), "firstName", "C2");
|
||||
return person;
|
||||
})
|
||||
.flatMap(repository::save)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(person -> {
|
||||
Map<String, Person> relatives = person.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("HAS_DAUGHTER", "HAS_SON");
|
||||
assertThat(relatives.get("HAS_DAUGHTER").getFirstName()).isEqualTo("C2");
|
||||
assertThat(relatives.get("HAS_SON").getFirstName()).isEqualTo("D");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
void shouldUpdateDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
repository.findById(idOfExistingPerson)
|
||||
.map(person -> {
|
||||
assumeThat(person).isNotNull();
|
||||
assumeThat(person.getName()).isEqualTo("A");
|
||||
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "DOGS");
|
||||
|
||||
pets.remove("DOGS");
|
||||
pets.get("CATS").add(new Pet("Delilah"));
|
||||
|
||||
pets.put("FISH", Collections.singletonList(new Pet("Nemo")));
|
||||
|
||||
return person;
|
||||
})
|
||||
.flatMap(repository::save)
|
||||
.as(StepVerifier::create)
|
||||
.consumeNextWith(person -> {
|
||||
Map<String, List<Pet>> pets = person.getPets();
|
||||
assertThat(pets).containsOnlyKeys("CATS", "FISH");
|
||||
assertThat(pets.get("CATS")).extracting(Pet::getName).containsExactlyInAnyOrder("Tom", "Garfield", "Delilah");
|
||||
assertThat(pets.get("FISH")).extracting(Pet::getName).containsExactlyInAnyOrder("Nemo");
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldWriteDynamicRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives newPerson = new PersonWithStringlyTypedRelatives("Test");
|
||||
Person d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R1");
|
||||
newPerson.getRelatives().put("RELATIVE_1", d);
|
||||
d = new Person();
|
||||
ReflectionTestUtils.setField(d, "firstName", "R2");
|
||||
newPerson.getRelatives().put("RELATIVE_2", d);
|
||||
|
||||
List<PersonWithStringlyTypedRelatives> recorded = new ArrayList<>();
|
||||
repository.save(newPerson)
|
||||
.as(StepVerifier::create)
|
||||
.recordWith(() -> recorded)
|
||||
.consumeNextWith(personWithRelatives -> {
|
||||
Map<String, Person> relatives = personWithRelatives.getRelatives();
|
||||
assertThat(relatives).containsOnlyKeys("RELATIVE_1", "RELATIVE_2");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Person))"
|
||||
+ " as numberOfRelations",
|
||||
Values.parameters("id", recorded.get(0).getId()))
|
||||
.single().get("numberOfRelations").asLong();
|
||||
assertThat(numberOfRelations).isEqualTo(2L);
|
||||
}
|
||||
}
|
||||
|
||||
@Test // GH-216
|
||||
void shouldWriteDynamicCollectionRelationships(@Autowired PersonWithRelativesRepository repository) {
|
||||
|
||||
PersonWithStringlyTypedRelatives newPerson = new PersonWithStringlyTypedRelatives("Test");
|
||||
Map<String, List<Pet>> pets = newPerson.getPets();
|
||||
|
||||
List<Pet> monsters = pets.computeIfAbsent("MONSTERS", s -> new ArrayList<>());
|
||||
monsters.add(new Pet("Godzilla"));
|
||||
monsters.add(new Pet("King Kong"));
|
||||
|
||||
List<Pet> fish = pets.computeIfAbsent("FISH", s -> new ArrayList<>());
|
||||
fish.add(new Pet("Nemo"));
|
||||
|
||||
List<PersonWithStringlyTypedRelatives> recorded = new ArrayList<>();
|
||||
repository.save(newPerson)
|
||||
.as(StepVerifier::create)
|
||||
.recordWith(() -> recorded)
|
||||
.consumeNextWith(person -> {
|
||||
Map<String, List<Pet>> writtenPets = person.getPets();
|
||||
assertThat(writtenPets).containsOnlyKeys("MONSTERS", "FISH");
|
||||
})
|
||||
.verifyComplete();
|
||||
|
||||
try (Transaction transaction = driver.session().beginTransaction()) {
|
||||
long numberOfRelations = transaction.run(""
|
||||
+ "MATCH (t:" + labelOfTestSubject + ") WHERE id(t) = $id "
|
||||
+ "RETURN size((t)-->(:Pet))"
|
||||
+ " as numberOfRelations", Values.parameters("id", recorded.get(0).getId()))
|
||||
.single().get("numberOfRelations").asLong();
|
||||
assertThat(numberOfRelations).isEqualTo(3L);
|
||||
}
|
||||
}
|
||||
|
||||
interface PersonWithRelativesRepository extends ReactiveNeo4jRepository<PersonWithStringlyTypedRelatives, Long> {
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement
|
||||
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
|
||||
static class Config extends AbstractReactiveNeo4jConfig {
|
||||
|
||||
@Bean
|
||||
public Driver driver() {
|
||||
return neo4jConnectionSupport.getDriver();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,9 @@
|
||||
*/
|
||||
package org.neo4j.springframework.data.integration.shared;
|
||||
|
||||
import java.lang.reflect.ParameterizedType;
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.neo4j.driver.Driver;
|
||||
import org.neo4j.driver.Session;
|
||||
@@ -28,11 +31,12 @@ import org.neo4j.springframework.data.test.Neo4jIntegrationTest;
|
||||
/**
|
||||
* Make sure that dynamic relationships can be loaded and stored.
|
||||
*
|
||||
* @param <T> Type of the person with relatives
|
||||
* @author Michael J. Simons
|
||||
* @soundtrack Helge Schneider - Live At The Grugahalle
|
||||
*/
|
||||
@Neo4jIntegrationTest
|
||||
public abstract class DynamicRelationshipsITBase {
|
||||
public abstract class DynamicRelationshipsITBase<T> {
|
||||
|
||||
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
|
||||
|
||||
@@ -40,8 +44,13 @@ public abstract class DynamicRelationshipsITBase {
|
||||
|
||||
protected long idOfExistingPerson;
|
||||
|
||||
protected final String labelOfTestSubject;
|
||||
|
||||
protected DynamicRelationshipsITBase(Driver driver) {
|
||||
this.driver = driver;
|
||||
Type type = getClass().getGenericSuperclass();
|
||||
String typeName = ((ParameterizedType) type).getActualTypeArguments()[0].getTypeName();
|
||||
this.labelOfTestSubject = typeName.substring(typeName.lastIndexOf(".") + 1);
|
||||
}
|
||||
|
||||
@BeforeEach
|
||||
@@ -52,7 +61,7 @@ public abstract class DynamicRelationshipsITBase {
|
||||
) {
|
||||
transaction.run("MATCH (n) detach delete n");
|
||||
idOfExistingPerson = transaction.run(""
|
||||
+ "CREATE (t:PersonWithRelatives {name: 'A'}) WITH t "
|
||||
+ "CREATE (t:" + labelOfTestSubject + " {name: 'A'}) WITH t "
|
||||
+ "CREATE (t) - [:HAS_WIFE] -> (w:Person {firstName: 'B'}) "
|
||||
+ "CREATE (t) - [:HAS_DAUGHTER] -> (d:Person {firstName: 'C'}) "
|
||||
+ "WITH t "
|
||||
|
||||
@@ -32,14 +32,28 @@ import org.neo4j.springframework.data.core.schema.Node;
|
||||
@Node
|
||||
public class PersonWithRelatives {
|
||||
|
||||
/**
|
||||
* Some enum representing relatives.
|
||||
*/
|
||||
public enum TypeOfRelative {
|
||||
HAS_WIFE, HAS_DAUGHTER, HAS_SON, RELATIVE_1, RELATIVE_2
|
||||
}
|
||||
|
||||
/**
|
||||
* Some enum representing pets.
|
||||
*/
|
||||
public enum TypeOfPet {
|
||||
CATS, DOGS, FISH, MONSTERS
|
||||
}
|
||||
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private final String name;
|
||||
|
||||
private Map<String, Person> relatives = new HashMap<>();
|
||||
private Map<TypeOfRelative, Person> relatives = new HashMap<>();
|
||||
|
||||
private Map<String, List<Pet>> pets = new HashMap<>();
|
||||
private Map<TypeOfPet, List<Pet>> pets = new HashMap<>();
|
||||
|
||||
public PersonWithRelatives(String name) {
|
||||
this.name = name;
|
||||
@@ -53,11 +67,11 @@ public class PersonWithRelatives {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Map<String, Person> getRelatives() {
|
||||
public Map<TypeOfRelative, Person> getRelatives() {
|
||||
return relatives;
|
||||
}
|
||||
|
||||
public Map<String, List<Pet>> getPets() {
|
||||
public Map<TypeOfPet, List<Pet>> getPets() {
|
||||
return pets;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) 2019-2020 "Neo4j,"
|
||||
* Neo4j Sweden AB [https://neo4j.com]
|
||||
*
|
||||
* This file is part of Neo4j.
|
||||
*
|
||||
* 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.neo4j.springframework.data.integration.shared;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.neo4j.springframework.data.core.schema.GeneratedValue;
|
||||
import org.neo4j.springframework.data.core.schema.Id;
|
||||
import org.neo4j.springframework.data.core.schema.Node;
|
||||
|
||||
/**
|
||||
* @author Michael J. Simons
|
||||
*/
|
||||
@Node
|
||||
public class PersonWithStringlyTypedRelatives {
|
||||
|
||||
@Id @GeneratedValue
|
||||
private Long id;
|
||||
|
||||
private final String name;
|
||||
|
||||
private Map<String, Person> relatives = new HashMap<>();
|
||||
|
||||
private Map<String, List<Pet>> pets = new HashMap<>();
|
||||
|
||||
public PersonWithStringlyTypedRelatives(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Map<String, Person> getRelatives() {
|
||||
return relatives;
|
||||
}
|
||||
|
||||
public Map<String, List<Pet>> getPets() {
|
||||
return pets;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user