DATAGRAPH-992 - extend support for contains keyword.

Spring Data's `contains` keyword gets mapped to a Neo4j `in` clause
for collections. Still supports the `contains` keyword for Strings.

(cherry picked from commit 32e2479)
This commit is contained in:
Gerrit Meier
2017-10-05 10:59:23 +02:00
parent 4eacdd7fae
commit b2706ccad4
5 changed files with 181 additions and 11 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
* Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
@@ -12,11 +12,22 @@
*/
package org.springframework.data.neo4j.repository.query.derived;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Stack;
import org.neo4j.ogm.cypher.BooleanOperator;
import org.neo4j.ogm.cypher.Filter;
import org.springframework.data.neo4j.repository.query.derived.builder.*;
import org.springframework.data.neo4j.repository.query.derived.builder.BetweenComparisonBuilder;
import org.springframework.data.neo4j.repository.query.derived.builder.BooleanComparisonBuilder;
import org.springframework.data.neo4j.repository.query.derived.builder.ContainsComparisonBuilder;
import org.springframework.data.neo4j.repository.query.derived.builder.DistanceComparisonBuilder;
import org.springframework.data.neo4j.repository.query.derived.builder.ExistsFilterBuilder;
import org.springframework.data.neo4j.repository.query.derived.builder.FilterBuilder;
import org.springframework.data.neo4j.repository.query.derived.builder.IsNullFilterBuilder;
import org.springframework.data.neo4j.repository.query.derived.builder.PropertyComparisonBuilder;
import org.springframework.data.repository.query.parser.Part;
/**
@@ -25,6 +36,7 @@ import org.springframework.data.repository.query.parser.Part;
* @author Luanne Misquitta
* @author Jasper Blues
* @author Nicolas Mervaillie
* @author Gerrit Meier
*/
public class CypherFinderQuery implements DerivedQueryDefinition {
@@ -76,7 +88,10 @@ public class CypherFinderQuery implements DerivedQueryDefinition {
return new DistanceComparisonBuilder(part, booleanOperator, entityType);
case BETWEEN:
return new BetweenComparisonBuilder(part, booleanOperator, entityType);
case IS_NULL:
case NOT_CONTAINING:
case CONTAINING:
return resolveMatchingContainsFilterBuilder(part, booleanOperator);
case IS_NULL:
case IS_NOT_NULL:
return new IsNullFilterBuilder(part, booleanOperator, entityType);
case EXISTS:
@@ -88,4 +103,12 @@ public class CypherFinderQuery implements DerivedQueryDefinition {
return new PropertyComparisonBuilder(part, booleanOperator, entityType);
}
}
private FilterBuilder resolveMatchingContainsFilterBuilder(Part part, BooleanOperator booleanOperator) {
boolean usePropertyComparison = !part.getProperty().getTypeInformation().isCollectionLike();
if (usePropertyComparison) {
return new PropertyComparisonBuilder(part, booleanOperator, entityType);
}
return new ContainsComparisonBuilder(part, booleanOperator, entityType);
}
}

View File

@@ -0,0 +1,50 @@
/*
* Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
*
* This product may include a number of subcomponents with
* separate copyright notices and license terms. Your use of the source
* code for these subcomponents is subject to the terms and
* conditions of the subcomponent's license, as noted in the LICENSE file.
*
*/
package org.springframework.data.neo4j.repository.query.derived.builder;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import org.neo4j.ogm.cypher.BooleanOperator;
import org.neo4j.ogm.cypher.ComparisonOperator;
import org.neo4j.ogm.cypher.Filter;
import org.neo4j.ogm.cypher.function.ContainsAnyComparison;
import org.springframework.data.repository.query.parser.Part;
/**
* Filter for entities having a collection like property (not) containing a given element.
*
* @author Gerrit Meier
*/
public class ContainsComparisonBuilder extends FilterBuilder {
public ContainsComparisonBuilder(Part part, BooleanOperator booleanOperator, Class<?> entityType) {
super(part, booleanOperator, entityType);
}
@Override
public List<Filter> build(Stack<Object> params) {
final Object containingValue = params.pop();
Filter containingFilter = new Filter(propertyName(), ComparisonOperator.IN, containingValue);
containingFilter.setOwnerEntityType(entityType);
containingFilter.setBooleanOperator(booleanOperator);
containingFilter.setNegated(isNegated());
containingFilter.setFunction(new ContainsAnyComparison(containingValue));
setNestedAttributes(part, containingFilter);
return Collections.singletonList(containingFilter);
}
}

View File

@@ -16,7 +16,15 @@ package org.springframework.data.neo4j.examples.movies;
import static org.junit.Assert.*;
import static org.neo4j.ogm.testutil.GraphTestUtils.*;
import java.util.*;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.TimeZone;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -33,14 +41,26 @@ import org.neo4j.ogm.session.SessionFactory;
import org.neo4j.ogm.testutil.MultiDriverTestClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.dao.DataAccessException;
import org.springframework.data.neo4j.examples.movies.domain.*;
import org.springframework.data.neo4j.examples.movies.repo.*;
import org.springframework.data.neo4j.examples.movies.domain.Actor;
import org.springframework.data.neo4j.examples.movies.domain.Cinema;
import org.springframework.data.neo4j.examples.movies.domain.Genre;
import org.springframework.data.neo4j.examples.movies.domain.Movie;
import org.springframework.data.neo4j.examples.movies.domain.Rating;
import org.springframework.data.neo4j.examples.movies.domain.ReleasedMovie;
import org.springframework.data.neo4j.examples.movies.domain.TempMovie;
import org.springframework.data.neo4j.examples.movies.domain.User;
import org.springframework.data.neo4j.examples.movies.repo.AbstractAnnotatedEntityRepository;
import org.springframework.data.neo4j.examples.movies.repo.AbstractEntityRepository;
import org.springframework.data.neo4j.examples.movies.repo.ActorRepository;
import org.springframework.data.neo4j.examples.movies.repo.CinemaRepository;
import org.springframework.data.neo4j.examples.movies.repo.RatingRepository;
import org.springframework.data.neo4j.examples.movies.repo.TempMovieRepository;
import org.springframework.data.neo4j.examples.movies.repo.UserRepository;
import org.springframework.data.neo4j.examples.movies.service.UserService;
import org.springframework.data.neo4j.repository.config.EnableNeo4jRepositories;
import org.springframework.data.neo4j.transaction.Neo4jTransactionManager;
@@ -59,11 +79,15 @@ import org.springframework.transaction.support.TransactionTemplate;
* @author Mark Angrish
* @author Mark Paluch
* @author Jens Schauder
* @author Gerrit Meier
*/
@ContextConfiguration(classes = {MoviesIntegrationTests.MoviesContext.class})
@RunWith(SpringJUnit4ClassRunner.class)
public class MoviesIntegrationTests extends MultiDriverTestClass {
private static final String KNOWN_MAIL_ADDRESS_1 = "a@example.org";
private static final String KNOWN_MAIL_ADDRESS_2 = "b@example.org";
private static final String UNKNOWN_MAIL_ADDRESS = "c@example.org";
private final Logger logger = LoggerFactory.getLogger(MoviesIntegrationTests.class);
@Autowired
@@ -650,6 +674,62 @@ public class MoviesIntegrationTests extends MultiDriverTestClass {
}
}
/**
* @see DATAGRAPH-992
*/
@Test
public void findUserByContainingEmailAddresses() {
createUserForContainsTest();
User foundUser = userRepository.findByEmailAddressesContains(Collections.singletonList(KNOWN_MAIL_ADDRESS_1));
assertNotNull(foundUser);
foundUser = userRepository.findByEmailAddressesContains(Arrays.asList(KNOWN_MAIL_ADDRESS_2, UNKNOWN_MAIL_ADDRESS));
assertNotNull(foundUser);
}
/**
* @see DATAGRAPH-992
*/
@Test
public void findNoUserByContainingEmailAddresses() {
createUserForContainsTest();
User foundUser = userRepository.findByEmailAddressesContains(Collections.singletonList(UNKNOWN_MAIL_ADDRESS));
assertNull(foundUser);
}
/**
* @see DATAGRAPH-992
*/
@Test
public void findUserByNotContainingEmailAddresses() {
createUserForContainsTest();
List<User> foundUser = userRepository.findByEmailAddressesNotContaining(UNKNOWN_MAIL_ADDRESS);
assertNotNull(foundUser.get(0));
}
/**
* @see DATAGRAPH-992
*/
@Test
public void findNoUserByNotContainingEmailAddresses() {
createUserForContainsTest();
List<User> foundUser = userRepository.findByEmailAddressesNotContaining(KNOWN_MAIL_ADDRESS_1);
assertTrue(foundUser.isEmpty());
}
private void createUserForContainsTest() {
User user = new User("Somebody");
Set<String> emailAddresses = new HashSet<>();
emailAddresses.add(KNOWN_MAIL_ADDRESS_1);
emailAddresses.add(KNOWN_MAIL_ADDRESS_2);
user.setEmailAddresses(emailAddresses);
userRepository.save(user);
}
private Calendar createDate(int y, int m, int d, String tz) {
Calendar calendar = Calendar.getInstance();

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
* Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
@@ -22,6 +22,7 @@ import java.util.Set;
/**
* @author Michal Bachman
* @author Luanne Misquitta
* @author Gerrit Meier
*/
public class User extends Person{
@@ -36,7 +37,10 @@ public class User extends Person{
@Relationship(type = "RATED")
private Set<Rating> ratings = new HashSet<>();
public User() {
private Set<String> emailAddresses;
public User() {
}
public User(String name) {
@@ -89,4 +93,12 @@ public class User extends Person{
public String getSurname() {
return surname;
}
public Set<String> getEmailAddresses() {
return emailAddresses;
}
public void setEmailAddresses(Set<String> emailAddresses) {
this.emailAddresses = emailAddresses;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright (c) [2011-2016] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
* Copyright (c) [2011-2017] "Pivotal Software, Inc." / "Neo Technology" / "Graph Aware Ltd."
*
* This product is licensed to you under the Apache License, Version 2.0 (the "License").
* You may not use this product except in compliance with the License.
@@ -34,6 +34,7 @@ import org.springframework.stereotype.Repository;
/**
* @author Michal Bachman
* @author Luanne Misquitta
* @author Gerrit Meier
*/
@Repository
public interface UserRepository extends PersonRepository<User, Long> {
@@ -127,4 +128,8 @@ public interface UserRepository extends PersonRepository<User, Long> {
@Query("invalid")
void invalidQuery();
User findByEmailAddressesContains(List<String> emails);
List<User> findByEmailAddressesNotContaining(String email);
}