GH-2361 - Add support for ReactiveQuerydslPredicateExecutor.
Closes #2361. Original pull request: #2360.
This commit is contained in:
committed by
Mark Paluch
parent
d996d88ccc
commit
de187219a9
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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.");
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user