GH-2361 - Add support for ReactiveQuerydslPredicateExecutor.

Closes #2361.
Original pull request: #2360.
This commit is contained in:
Michael Simons
2021-08-26 12:20:09 +02:00
committed by Mark Paluch
parent d996d88ccc
commit de187219a9
5 changed files with 644 additions and 6 deletions

View File

@@ -0,0 +1,174 @@
/*
* Copyright 2011-2021 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.repository.query;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.function.Function;
import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.Cypher;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.lang.Nullable;
import com.querydsl.core.types.Predicate;
/**
* Immutable implementation of a {@link ReactiveFluentQuery}. All
* methods that return a {@link ReactiveFluentQuery} return a new instance, the original instance won't be
* modified.
*
* @author Michael J. Simons
* @param <S> Source type
* @param <R> Result type
* @since 6.2
*/
@API(status = API.Status.INTERNAL, since = "6.2") final class ReactiveFluentQueryByPredicate<S, R>
extends FluentQuerySupport<R> implements ReactiveFluentQuery<R> {
private final Predicate predicate;
private final Neo4jPersistentEntity<S> metaData;
private final ReactiveFluentFindOperation findOperation;
private final Function<Predicate, Mono<Long>> countOperation;
private final Function<Predicate, Mono<Boolean>> existsOperation;
ReactiveFluentQueryByPredicate(
Predicate predicate,
Neo4jPersistentEntity<S> metaData,
Class<R> resultType,
ReactiveFluentFindOperation findOperation,
Function<Predicate, Mono<Long>> countOperation,
Function<Predicate, Mono<Boolean>> existsOperation
) {
this(predicate, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null);
}
ReactiveFluentQueryByPredicate(
Predicate predicate,
Neo4jPersistentEntity<S> metaData,
Class<R> resultType,
ReactiveFluentFindOperation findOperation,
Function<Predicate, Mono<Long>> countOperation,
Function<Predicate, Mono<Boolean>> existsOperation,
Sort sort,
@Nullable Collection<String> properties
) {
super(resultType, sort, properties);
this.predicate = predicate;
this.metaData = metaData;
this.findOperation = findOperation;
this.countOperation = countOperation;
this.existsOperation = existsOperation;
}
@Override
@SuppressWarnings("HiddenField")
public ReactiveFluentQuery<R> sortBy(Sort sort) {
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
}
@Override
@SuppressWarnings("HiddenField")
public <NR> ReactiveFluentQuery<NR> as(Class<NR> resultType) {
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
this.countOperation, this.existsOperation);
}
@Override
@SuppressWarnings("HiddenField")
public ReactiveFluentQuery<R> project(Collection<String> properties) {
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
this.countOperation, this.existsOperation, sort, mergeProperties(properties));
}
@Override
public Mono<R> one() {
return findOperation.find(metaData.getType())
.as(resultType)
.matching(
QueryFragmentsAndParameters.forCondition(metaData,
Cypher.adapt(predicate).asCondition(),
null,
CypherAdapterUtils.toSortItems(this.metaData, sort),
createIncludedFieldsPredicate()))
.one();
}
@Override
public Mono<R> first() {
return all().take(1).singleOrEmpty();
}
@Override
public Flux<R> all() {
return findOperation.find(metaData.getType())
.as(resultType)
.matching(
QueryFragmentsAndParameters.forCondition(metaData,
Cypher.adapt(predicate).asCondition(),
null,
CypherAdapterUtils.toSortItems(this.metaData, sort),
createIncludedFieldsPredicate()))
.all();
}
@Override
public Mono<Page<R>> page(Pageable pageable) {
Flux<R> results = findOperation.find(metaData.getType())
.as(resultType)
.matching(
QueryFragmentsAndParameters.forCondition(metaData,
Cypher.adapt(predicate).asCondition(),
pageable, null,
createIncludedFieldsPredicate()))
.all();
return results.collectList().zipWith(countOperation.apply(predicate)).map(tuple -> {
Page<R> page = PageableExecutionUtils.getPage(tuple.getT1(), pageable, () -> tuple.getT2());
return page;
});
}
@Override
public Mono<Long> count() {
return countOperation.apply(predicate);
}
@Override
public Mono<Boolean> exists() {
return existsOperation.apply(predicate);
}
}

View File

@@ -0,0 +1,142 @@
/*
* Copyright 2011-2021 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.repository.query;
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.Arrays;
import java.util.Collection;
import java.util.function.Function;
import org.apiguardian.api.API;
import org.neo4j.cypherdsl.core.Condition;
import org.neo4j.cypherdsl.core.Conditions;
import org.neo4j.cypherdsl.core.Cypher;
import org.neo4j.cypherdsl.core.Functions;
import org.neo4j.cypherdsl.core.SortItem;
import org.neo4j.cypherdsl.core.Statement;
import org.reactivestreams.Publisher;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.repository.support.Neo4jEntityInformation;
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Predicate;
/**
* Querydsl specific fragment for extending {@link org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository}
* with an implementation of {@link ReactiveQuerydslPredicateExecutor}. Provides the necessary infrastructure for translating
* Query-DSL predicates into conditions that are passed along to the Cypher-DSL and eventually to the template infrastructure.
* This fragment will be loaded by the repository infrastructure when a repository is declared extending the above interface.
*
* @author Michael J. Simons
* @param <T> The returned domain type.
* @since 6.2
*/
@API(status = API.Status.INTERNAL, since = "6.2")
public final class ReactiveQuerydslNeo4jPredicateExecutor<T> implements ReactiveQuerydslPredicateExecutor<T> {
private final Neo4jEntityInformation<T, Object> entityInformation;
private final ReactiveNeo4jOperations neo4jOperations;
private final Neo4jPersistentEntity<T> metaData;
public ReactiveQuerydslNeo4jPredicateExecutor(Neo4jEntityInformation<T, Object> entityInformation,
ReactiveNeo4jOperations neo4jOperations) {
this.entityInformation = entityInformation;
this.neo4jOperations = neo4jOperations;
this.metaData = this.entityInformation.getEntityMetaData();
}
@Override
public Mono<T> findOne(Predicate predicate) {
return this.neo4jOperations.toExecutableQuery(
this.metaData.getType(),
QueryFragmentsAndParameters.forCondition(this.metaData, Cypher.adapt(predicate).asCondition(), null,
null)
).flatMap(ReactiveNeo4jOperations.ExecutableQuery::getSingleResult);
}
@Override
public Flux<T> findAll(Predicate predicate) {
return doFindAll(Cypher.adapt(predicate).asCondition(), null);
}
@Override
public Flux<T> findAll(Predicate predicate, Sort sort) {
return doFindAll(Cypher.adapt(predicate).asCondition(), CypherAdapterUtils.toSortItems(this.metaData, sort));
}
@Override
public Flux<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
return doFindAll(Cypher.adapt(predicate).asCondition(), Arrays.asList(QuerydslNeo4jPredicateExecutor.toSortItems(orders)));
}
@Override
public Flux<T> findAll(OrderSpecifier<?>... orders) {
return doFindAll(Conditions.noCondition(), Arrays.asList(QuerydslNeo4jPredicateExecutor.toSortItems(orders)));
}
private Flux<T> doFindAll(Condition condition, Collection<SortItem> sortItems) {
return this.neo4jOperations.toExecutableQuery(
this.metaData.getType(),
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null,
sortItems)
).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
}
@Override
public Mono<Long> count(Predicate predicate) {
Statement statement = CypherGenerator.INSTANCE.prepareMatchOf(this.metaData,
Cypher.adapt(predicate).asCondition())
.returning(Functions.count(asterisk())).build();
return this.neo4jOperations.count(statement, statement.getParameters());
}
@Override
public Mono<Boolean> exists(Predicate predicate) {
return findAll(predicate).hasElements();
}
@Override
public <S extends T, R, P extends Publisher<R>> P findBy(Predicate predicate, Function<ReactiveFluentQuery<S>, P> queryFunction) {
if (this.neo4jOperations instanceof ReactiveFluentFindOperation) {
@SuppressWarnings("unchecked") // defaultResultType will be a supertype of S and at this stage, the same.
ReactiveFluentQuery<S> fluentQuery = (ReactiveFluentQuery<S>) new ReactiveFluentQueryByPredicate<>(predicate, metaData, metaData.getType(),
(ReactiveFluentFindOperation) this.neo4jOperations, this::count, this::exists);
return queryFunction.apply(fluentQuery);
}
throw new UnsupportedOperationException(
"Fluent find by example not supported with standard Neo4jOperations. Must support fluent queries too.");
}
}

View File

@@ -17,7 +17,6 @@ package org.springframework.data.neo4j.repository.support;
import java.util.Optional;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.neo4j.core.Neo4jOperations;
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
@@ -102,11 +101,6 @@ final class Neo4jRepositoryFactory extends RepositoryFactorySupport {
private RepositoryFragment<Object> createDSLExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
if (metadata.isReactiveRepository()) {
throw new InvalidDataAccessApiUsageException(
"Cannot combine DSL executor and reactive repository support in a single interface");
}
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
Object querydslFragment = instantiateClass(implementor, entityInformation, neo4jOperations);

View File

@@ -25,7 +25,10 @@ import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
import org.springframework.data.neo4j.repository.query.ReactiveNeo4jQueryLookupStrategy;
import org.springframework.data.neo4j.repository.query.ReactiveQuerydslNeo4jPredicateExecutor;
import org.springframework.data.neo4j.repository.query.SimpleReactiveQueryByExampleExecutor;
import org.springframework.data.querydsl.QuerydslUtils;
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
@@ -80,9 +83,25 @@ final class ReactiveNeo4jRepositoryFactory extends ReactiveRepositoryFactorySupp
fragments = fragments.append(RepositoryFragment.implemented(byExampleExecutor));
boolean isQueryDslRepository = QuerydslUtils.QUERY_DSL_PRESENT
&& ReactiveQuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
if (isQueryDslRepository) {
fragments = fragments.append(createDSLExecutorFragment(metadata, ReactiveQuerydslNeo4jPredicateExecutor.class));
}
return fragments;
}
private RepositoryFragment<Object> createDSLExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
Object querydslFragment = instantiateClass(implementor, entityInformation, neo4jOperations);
return RepositoryFragment.implemented(querydslFragment);
}
@Override
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleReactiveNeo4jRepository.class;

View File

@@ -0,0 +1,309 @@
/*
* Copyright 2011-2021 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.reactive;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.test.StepVerifier;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.neo4j.driver.Driver;
import org.neo4j.driver.Session;
import org.neo4j.driver.Transaction;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.neo4j.config.AbstractReactiveNeo4jConfig;
import org.springframework.data.neo4j.core.ReactiveDatabaseSelectionProvider;
import org.springframework.data.neo4j.core.transaction.Neo4jBookmarkManager;
import org.springframework.data.neo4j.core.transaction.ReactiveNeo4jTransactionManager;
import org.springframework.data.neo4j.integration.shared.common.Person;
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
import org.springframework.data.neo4j.repository.config.EnableReactiveNeo4jRepositories;
import org.springframework.data.neo4j.test.BookmarkCapture;
import org.springframework.data.neo4j.test.Neo4jExtension;
import org.springframework.data.neo4j.test.Neo4jIntegrationTest;
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
import org.springframework.transaction.ReactiveTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import com.querydsl.core.types.Ops;
import com.querydsl.core.types.Order;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Path;
import com.querydsl.core.types.Predicate;
import com.querydsl.core.types.dsl.Expressions;
/**
* @author Michael J. Simons
*/
@Neo4jIntegrationTest
class ReactiveQuerydslNeo4jPredicateExecutorIT {
protected static Neo4jExtension.Neo4jConnectionSupport neo4jConnectionSupport;
private final Path<Person> personPath;
private final Path<String> firstNamePath;
private final Path<String> lastNamePath;
ReactiveQuerydslNeo4jPredicateExecutorIT() {
this.personPath = Expressions.path(Person.class, "person");
this.firstNamePath = Expressions.path(String.class, personPath, "firstName");
this.lastNamePath = Expressions.path(String.class, personPath, "lastName");
}
@BeforeAll
protected static void setupData(@Autowired BookmarkCapture bookmarkCapture) {
try (Session session = neo4jConnectionSupport.getDriver().session(bookmarkCapture.createSessionConfig());
Transaction transaction = session.beginTransaction()
) {
transaction.run("MATCH (n) detach delete n");
transaction.run("CREATE (p:Person{firstName: 'A', lastName: 'LA'})");
transaction.run("CREATE (p:Person{firstName: 'B', lastName: 'LB'})");
transaction
.run("CREATE (p:Person{firstName: 'Helge', lastName: 'Schneider'}) -[:LIVES_AT]-> (a:Address {city: 'Mülheim an der Ruhr'})");
transaction.run("CREATE (p:Person{firstName: 'Bela', lastName: 'B.'})");
transaction.commit();
bookmarkCapture.seedWith(session.lastBookmark());
}
}
@Test // GH-2361
void fluentFindOneShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"));
repository.findBy(predicate, q -> q.one())
.map(Person::getLastName)
.as(StepVerifier::create)
.expectNext("Schneider")
.verifyComplete();
}
@Test // GH-2361
void fluentFindAllShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
repository.findBy(predicate, q -> q.all())
.map(Person::getFirstName)
.sort() // Due to not having something like containsExactlyInAnyOrder
.as(StepVerifier::create)
.expectNext("Bela", "Helge")
.verifyComplete();
}
@Test // GH-2361
void fluentFindAllProjectingShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"));
repository.findBy(predicate, q -> q.project("firstName").all())
.as(StepVerifier::create)
.expectNextMatches(p -> {
assertThat(p.getFirstName()).isEqualTo("Helge");
assertThat(p.getId()).isNotNull();
assertThat(p.getLastName()).isNull();
assertThat(p.getAddress()).isNull();
return true;
})
.verifyComplete();
}
static class DtoPersonProjection {
private final String firstName;
DtoPersonProjection(String firstName) {
this.firstName = firstName;
}
public String getFirstName() {
return firstName;
}
}
@Test // GH-2361
void fluentfindAllAsShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"));
repository.findBy(predicate, q -> q.as(DtoPersonProjection.class).all())
.map(DtoPersonProjection::getFirstName)
.as(StepVerifier::create)
.expectNext("Helge")
.verifyComplete();
}
@Test // GH-2361
void fluentFindFirstShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.TRUE.isTrue();
repository.findBy(predicate, q -> q.sortBy(Sort.by(Sort.Direction.DESC, "lastName")).first())
.map(Person::getFirstName)
.as(StepVerifier::create)
.expectNext("Helge")
.verifyComplete();
}
@Test // GH-2361
void fluentFindAllWithSortShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.TRUE.isTrue();
repository.findBy(predicate, q -> q.sortBy(Sort.by(Sort.Direction.DESC, "lastName")).all())
.map(Person::getLastName)
.as(StepVerifier::create)
.expectNext("Schneider", "LB", "LA", "B.")
.verifyComplete();
}
@Test // GH-2361
void fluentFindAllWithPaginationShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
repository.findBy(predicate, q -> q.page(PageRequest.of(1, 1, Sort.by("lastName").ascending())))
.as(StepVerifier::create)
.expectNextMatches(people -> {
assertThat(people).extracting(Person::getFirstName).containsExactly("Helge");
assertThat(people.hasPrevious()).isTrue();
assertThat(people.hasNext()).isFalse();
return true;
}).verifyComplete();
}
@Test // GH-2361
void fluentExistsShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"));
repository.findBy(predicate, q -> q.exists()).as(StepVerifier::create).expectNext(true).verifyComplete();
}
@Test // GH-2361
void fluentCountShouldWork(@Autowired QueryDSLPersonRepository repository) {
Predicate predicate = Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")));
repository.findBy(predicate, q -> q.count()).as(StepVerifier::create).expectNext(2L).verifyComplete();
}
@Test // GH-2361
void findOneShouldWork(@Autowired QueryDSLPersonRepository repository) {
repository.findOne(Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge")))
.map(Person::getLastName)
.as(StepVerifier::create)
.expectNext("Schneider")
.verifyComplete();
}
@Test // GH-2361
void findAllShouldWork(@Autowired QueryDSLPersonRepository repository) {
repository.findAll(Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B."))))
.map(Person::getFirstName)
.sort() // Due to not having something like containsExactlyInAnyOrder
.as(StepVerifier::create)
.expectNext("Bela", "Helge")
.verifyComplete();
}
@Test // GH-2361
void sortedFindAllShouldWork(@Autowired QueryDSLPersonRepository repository) {
repository.findAll(Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B."))),
new OrderSpecifier(Order.DESC, lastNamePath)
)
.map(Person::getFirstName)
.as(StepVerifier::create)
.expectNext("Helge", "Bela")
.verifyComplete();
}
@Test // GH-2361
void orderedFindAllShouldWork(@Autowired QueryDSLPersonRepository repository) {
repository.findAll(Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B."))),
Sort.by("lastName").descending()
)
.map(Person::getFirstName)
.as(StepVerifier::create)
.expectNext("Helge", "Bela")
.verifyComplete();
}
@Test // GH-2361
void orderedFindAllWithoutPredicateShouldWork(@Autowired QueryDSLPersonRepository repository) {
repository.findAll(new OrderSpecifier(Order.DESC, lastNamePath))
.map(Person::getFirstName)
.as(StepVerifier::create)
.expectNext("Helge", "B", "A", "Bela")
.verifyComplete();
}
@Test // GH-2361
void countShouldWork(@Autowired QueryDSLPersonRepository repository) {
repository.count(Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("Helge"))
.or(Expressions.predicate(Ops.EQ, lastNamePath, Expressions.asString("B.")))
).as(StepVerifier::create)
.expectNext(2L)
.verifyComplete();
}
@Test // GH-2361
void existsShouldWork(@Autowired QueryDSLPersonRepository repository) {
repository.exists(Expressions.predicate(Ops.EQ, firstNamePath, Expressions.asString("A")))
.as(StepVerifier::create)
.expectNext(true)
.verifyComplete();
}
interface QueryDSLPersonRepository extends ReactiveNeo4jRepository<Person, Long>, ReactiveQuerydslPredicateExecutor<Person> {
}
@Configuration
@EnableTransactionManagement
@EnableReactiveNeo4jRepositories(considerNestedRepositories = true)
static class Config extends AbstractReactiveNeo4jConfig {
@Bean
public Driver driver() {
return neo4jConnectionSupport.getDriver();
}
@Bean
public BookmarkCapture bookmarkCapture() {
return new BookmarkCapture();
}
@Override
public ReactiveTransactionManager reactiveTransactionManager(Driver driver, ReactiveDatabaseSelectionProvider databaseNameProvider) {
BookmarkCapture bookmarkCapture = bookmarkCapture();
return new ReactiveNeo4jTransactionManager(driver, databaseNameProvider, Neo4jBookmarkManager.create(bookmarkCapture));
}
}
}