DATAGRAPH-1351 - Apply possible conversions for ids during delete.

Co-authored-by: Dennis Crissman <dennis@tabbycatapps.com>
Co-authored-by: Rosetta Roberts <rosettafroberts@gmail.com>
This commit is contained in:
Michael Simons
2020-08-06 17:52:44 +02:00
committed by GitHub
parent f1518d6dee
commit 3af755512a
6 changed files with 241 additions and 26 deletions

View File

@@ -316,7 +316,7 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
log.debug(() -> String.format("Deleting entity with id %s ", id));
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
ResultSummary summary = this.neo4jClient.query(renderer.render(statement)).in(getDatabaseName()).bind(id)
ResultSummary summary = this.neo4jClient.query(renderer.render(statement)).in(getDatabaseName()).bind(convertIdValues(id))
.to(nameOfParameter).run();
log.debug(() -> String.format("Deleted %d nodes and %d relationships.", summary.counters().nodesDeleted(),
@@ -333,7 +333,7 @@ public final class Neo4jTemplate implements Neo4jOperations, BeanFactoryAware {
log.debug(() -> String.format("Deleting all entities with the following ids: %s ", ids));
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
ResultSummary summary = this.neo4jClient.query(renderer.render(statement)).in(getDatabaseName()).bind(ids)
ResultSummary summary = this.neo4jClient.query(renderer.render(statement)).in(getDatabaseName()).bind(convertIdValues(ids))
.to(nameOfParameter).run();
log.debug(() -> String.format("Deleted %d nodes and %d relationships.", summary.counters().nodesDeleted(),

View File

@@ -329,7 +329,7 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
return getDatabaseName().flatMap(databaseName -> this.neo4jClient.query(() -> renderer.render(statement))
.in(databaseName.getValue()).bind(ids).to(nameOfParameter).run().then());
.in(databaseName.getValue()).bind(convertIdValues(ids)).to(nameOfParameter).run().then());
}
@Override
@@ -343,7 +343,7 @@ public final class ReactiveNeo4jTemplate implements ReactiveNeo4jOperations, Bea
Statement statement = cypherGenerator.prepareDeleteOf(entityMetaData, condition);
return getDatabaseName().flatMap(databaseName -> this.neo4jClient.query(() -> renderer.render(statement))
.in(databaseName.getValue()).bind(id).to(nameOfParameter).run().then());
.in(databaseName.getValue()).bind(convertIdValues(id)).to(nameOfParameter).run().then());
}
@Override

View File

@@ -172,7 +172,7 @@ class DefaultNeo4jPersistentProperty extends AnnotationBasedPersistentProperty<N
String propertyName = this.graphPropertyName.getNullable();
if (propertyName == null) {
throw new MappingException("This property is not mapped to a Graph property!");
throw new MappingException("The property '" + propertyName + "' is not mapped to a Graph property!");
}
return propertyName;

View File

@@ -15,8 +15,9 @@
*/
package org.springframework.data.neo4j.integration.imperative;
import static java.util.Collections.*;
import static org.assertj.core.api.Assertions.*;
import static java.util.Collections.singletonList;
import static java.util.Collections.singletonMap;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Arrays;
import java.util.Collection;
@@ -24,7 +25,10 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -38,14 +42,18 @@ import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.Transaction;
import org.neo4j.driver.TransactionWork;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import org.neo4j.driver.summary.ResultSummary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.AbstractNeo4jConfig;
import org.springframework.data.neo4j.core.Neo4jOperations;
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
import org.springframework.data.neo4j.integration.shared.PersonWithAllConstructor;
import org.springframework.data.neo4j.integration.shared.PersonWithCustomId;
import org.springframework.data.neo4j.integration.shared.ThingWithGeneratedId;
import org.springframework.data.neo4j.test.Neo4jExtension.Neo4jConnectionSupport;
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
@@ -54,6 +62,7 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* @author Gerrit Meier
* @author Michael J. Simons
* @author Rosetta Roberts
*/
@Neo4jIntegrationTest
class Neo4jOperationsIT {
@@ -65,6 +74,7 @@ class Neo4jOperationsIT {
private final Driver driver;
private final Neo4jOperations neo4jOperations;
private final AtomicLong customIdValueGenerator = new AtomicLong();
private Long person1Id;
private Long person2Id;
@@ -81,23 +91,25 @@ class Neo4jOperationsIT {
* @return The session config used for verification methods.
*/
SessionConfig getSessionConfig() {
return SessionConfig.defaultConfig();
}
@BeforeEach
void setupData() {
Transaction transaction = driver.session(getSessionConfig()).beginTransaction();
transaction.run("MATCH (n) detach delete n");
try (
Session session = driver.session(getSessionConfig());
Transaction transaction = session.beginTransaction();
) {
transaction.run("MATCH (n) detach delete n");
person1Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n)",
Values.parameters("name", TEST_PERSON1_NAME)).next().get(0).asLong();
person2Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n)",
Values.parameters("name", TEST_PERSON2_NAME)).next().get(0).asLong();
person1Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n) AS id",
Values.parameters("name", TEST_PERSON1_NAME)).single().get("id").asLong();
person2Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n) AS id",
Values.parameters("name", TEST_PERSON2_NAME)).single().get("id").asLong();
transaction.commit();
transaction.close();
transaction.commit();
}
}
@Test
@@ -157,8 +169,9 @@ class Neo4jOperationsIT {
Statement statement = Cypher.match(node).where(node.property("name").isEqualTo(Cypher.parameter("name")))
.returning(node).build();
List<PersonWithAllConstructor> people = neo4jOperations.findAll(statement, singletonMap("name", TEST_PERSON1_NAME),
PersonWithAllConstructor.class);
List<PersonWithAllConstructor> people = neo4jOperations
.findAll(statement, singletonMap("name", TEST_PERSON1_NAME),
PersonWithAllConstructor.class);
assertThat(people).hasSize(1);
}
@@ -276,15 +289,67 @@ class Neo4jOperationsIT {
}
}
TransactionWork<ResultSummary> createPersonWithCustomId(PersonWithCustomId.PersonId assignedId) {
return tx -> tx.run("CREATE (n:PersonWithCustomId) SET n.id = $id ",
Values.parameters("id", assignedId.getId())).consume();
}
@Test
void deleteByCustomId() {
PersonWithCustomId.PersonId id = new PersonWithCustomId.PersonId(customIdValueGenerator.incrementAndGet());
try (Session session = driver.session(getSessionConfig())) {
session.writeTransaction(createPersonWithCustomId(id));
}
assertThat(neo4jOperations.count(PersonWithCustomId.class)).isEqualTo(1L);
neo4jOperations.deleteById(id, PersonWithCustomId.class);
try (Session session = driver.session(getSessionConfig())) {
Result result = session.run("MATCH (p:PersonWithCustomId) return count(p) as count");
assertThat(result.single().get("count").asLong()).isEqualTo(0);
}
}
@Test
void deleteAllByCustomId() {
List<PersonWithCustomId.PersonId> ids = Stream.generate(customIdValueGenerator::incrementAndGet)
.map(PersonWithCustomId.PersonId::new)
.limit(2)
.collect(Collectors.toList());
try (
Session session = driver.session(getSessionConfig());
) {
ids.forEach(id -> session.writeTransaction(createPersonWithCustomId(id)));
}
assertThat(neo4jOperations.count(PersonWithCustomId.class)).isEqualTo(2L);
neo4jOperations.deleteAllById(ids, PersonWithCustomId.class);
try (Session session = driver.session(getSessionConfig())) {
Result result = session.run("MATCH (p:PersonWithCustomId) return count(p) as count");
assertThat(result.single().get("count").asLong()).isEqualTo(0);
}
}
@Configuration
@EnableTransactionManagement
static class Config extends AbstractNeo4jConfig {
@Bean
@Override
public Driver driver() {
return neo4jConnectionSupport.getDriver();
}
@Bean
@Override
public Neo4jConversions neo4jConversions() {
return new Neo4jConversions(singletonList(new PersonWithCustomId.CustomPersonIdConverter()));
}
@Override // needed here because there is no implicit registration of entities upfront some methods under test
protected Collection<String> getMappingBasePackages() {
return singletonList(PersonWithAllConstructor.class.getPackage().getName());

View File

@@ -28,7 +28,10 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Tag;
@@ -43,14 +46,18 @@ import org.neo4j.driver.Result;
import org.neo4j.driver.Session;
import org.neo4j.driver.SessionConfig;
import org.neo4j.driver.Transaction;
import org.neo4j.driver.TransactionWork;
import org.neo4j.driver.Value;
import org.neo4j.driver.Values;
import org.neo4j.driver.summary.ResultSummary;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
import org.springframework.data.neo4j.core.convert.Neo4jConversions;
import org.springframework.data.neo4j.integration.shared.PersonWithAllConstructor;
import org.springframework.data.neo4j.integration.shared.PersonWithCustomId;
import org.springframework.data.neo4j.integration.shared.ThingWithGeneratedId;
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
import org.springframework.data.neo4j.test.Neo4jExtension.*;
@@ -71,6 +78,7 @@ class ReactiveNeo4jOperationsIT {
private final Driver driver;
private final ReactiveNeo4jOperations neo4jOperations;
private final AtomicLong customIdValueGenerator = new AtomicLong();
private Long person1Id;
private Long person2Id;
@@ -94,16 +102,19 @@ class ReactiveNeo4jOperationsIT {
@BeforeEach
void setupData() {
Transaction transaction = driver.session(getSessionConfig()).beginTransaction();
transaction.run("MATCH (n) detach delete n");
try (
Session session = driver.session(getSessionConfig());
Transaction transaction = session.beginTransaction();
) {
transaction.run("MATCH (n) detach delete n");
person1Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n)",
Values.parameters("name", TEST_PERSON1_NAME)).next().get(0).asLong();
person2Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n)",
Values.parameters("name", TEST_PERSON2_NAME)).next().get(0).asLong();
person1Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n) AS id",
Values.parameters("name", TEST_PERSON1_NAME)).single().get("id").asLong();
person2Id = transaction.run("CREATE (n:PersonWithAllConstructor) SET n.name = $name RETURN id(n) AS id",
Values.parameters("name", TEST_PERSON2_NAME)).single().get("id").asLong();
transaction.commit();
transaction.close();
transaction.commit();
}
}
@Test
@@ -265,8 +276,36 @@ class ReactiveNeo4jOperationsIT {
}
}
TransactionWork<ResultSummary> createPersonWithCustomId(PersonWithCustomId.PersonId assignedId) {
return tx -> tx.run("CREATE (n:PersonWithCustomId) SET n.id = $id ",
Values.parameters("id", assignedId.getId())).consume();
}
@Test
void deleteByCustomId() {
PersonWithCustomId.PersonId id = new PersonWithCustomId.PersonId(customIdValueGenerator.incrementAndGet());
try (Session session = driver.session(getSessionConfig())) {
session.writeTransaction(createPersonWithCustomId(id));
}
StepVerifier.create(neo4jOperations.count(PersonWithCustomId.class))
.expectNext(1L)
.verifyComplete();
StepVerifier.create(neo4jOperations.deleteById(id, PersonWithCustomId.class))
.verifyComplete();
try (Session session = driver.session(getSessionConfig())) {
Result result = session.run("MATCH (p:PersonWithCustomId) return count(p) as count");
assertThat(result.single().get("count").asLong()).isEqualTo(0);
}
}
@Test
void deleteAllById() {
StepVerifier
.create(neo4jOperations.deleteAllById(Arrays.asList(person1Id, person2Id), PersonWithAllConstructor.class))
.verifyComplete();
@@ -277,6 +316,32 @@ class ReactiveNeo4jOperationsIT {
}
}
@Test
void deleteAllByCustomId() {
List<PersonWithCustomId.PersonId> ids = Stream.generate(customIdValueGenerator::incrementAndGet)
.map(PersonWithCustomId.PersonId::new)
.limit(2)
.collect(Collectors.toList());
try (
Session session = driver.session(getSessionConfig());
) {
ids.forEach(id -> session.writeTransaction(createPersonWithCustomId(id)));
}
StepVerifier.create(neo4jOperations.count(PersonWithCustomId.class))
.expectNext(2L)
.verifyComplete();
StepVerifier.create(neo4jOperations.deleteAllById(ids, PersonWithCustomId.class))
.verifyComplete();
try (Session session = driver.session(getSessionConfig())) {
Result result = session.run("MATCH (p:PersonWithCustomId) return count(p) as count");
assertThat(result.single().get("count").asLong()).isEqualTo(0);
}
}
@Configuration
@EnableTransactionManagement
static class Config extends AbstractReactiveNeo4jConfig {
@@ -286,6 +351,12 @@ class ReactiveNeo4jOperationsIT {
return neo4jConnectionSupport.getDriver();
}
@Bean
@Override
public Neo4jConversions neo4jConversions() {
return new Neo4jConversions(singletonList(new PersonWithCustomId.CustomPersonIdConverter()));
}
@Override // needed here because there is no implicit registration of entities upfront some methods under test
protected Collection<String> getMappingBasePackages() {
return singletonList(PersonWithAllConstructor.class.getPackage().getName());

View File

@@ -0,0 +1,79 @@
/*
* Copyright 2011-2020 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.shared;
import lombok.Value;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.neo4j.driver.Values;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.GenericConverter;
import org.springframework.data.neo4j.core.schema.Id;
/**
* @author Rosetta Roberts
* @author Michael J. Simons
*/
@Value
public class PersonWithCustomId {
/**
* Custom ID type for a person object.
*/
@Value
public static class PersonId {
// Be aware that this is not the native (aka generated) Neo4j id.
// Natively generated IDs are only possible directly on a long field.
// This is an assigned id.
private final Long id;
}
/**
* Converted needed to deal with the above custom type. Without that converter, an association would be assumed.
*/
public static class CustomPersonIdConverter implements GenericConverter {
@Override
public Set<ConvertiblePair> getConvertibleTypes() {
return new HashSet<>(Arrays.asList(
new ConvertiblePair(PersonId.class, org.neo4j.driver.Value.class),
new ConvertiblePair(org.neo4j.driver.Value.class, PersonId.class)
));
}
@Override
public Object convert(Object o, TypeDescriptor type1, TypeDescriptor type2) {
if (o == null) {
return null;
}
if (PersonId.class.isAssignableFrom(type1.getType())) {
return Values.value(((PersonId) o).getId());
} else {
return new PersonId(((org.neo4j.driver.Value) o).asLong());
}
}
}
@Id
private final PersonId id;
private final String name;
}