Skip to content

Commit b45d62c

Browse files
GH-2361 - Add support for ReactiveQuerydslPredicateExecutor.
Closes #2361.
1 parent 7ad2bf7 commit b45d62c

File tree

5 files changed

+644
-6
lines changed

5 files changed

+644
-6
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.repository.query;
17+
18+
import reactor.core.publisher.Flux;
19+
import reactor.core.publisher.Mono;
20+
21+
import java.util.Collection;
22+
import java.util.function.Function;
23+
24+
import org.apiguardian.api.API;
25+
import org.neo4j.cypherdsl.core.Cypher;
26+
import org.springframework.data.domain.Page;
27+
import org.springframework.data.domain.Pageable;
28+
import org.springframework.data.domain.Sort;
29+
import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
30+
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
31+
import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
32+
import org.springframework.data.support.PageableExecutionUtils;
33+
import org.springframework.lang.Nullable;
34+
35+
import com.querydsl.core.types.Predicate;
36+
37+
/**
38+
* Immutable implementation of a {@link ReactiveFluentQuery}. All
39+
* methods that return a {@link ReactiveFluentQuery} return a new instance, the original instance won't be
40+
* modified.
41+
*
42+
* @author Michael J. Simons
43+
* @param <S> Source type
44+
* @param <R> Result type
45+
* @since 6.2
46+
*/
47+
@API(status = API.Status.INTERNAL, since = "6.2") final class ReactiveFluentQueryByPredicate<S, R>
48+
extends FluentQuerySupport<R> implements ReactiveFluentQuery<R> {
49+
50+
private final Predicate predicate;
51+
52+
private final Neo4jPersistentEntity<S> metaData;
53+
54+
private final ReactiveFluentFindOperation findOperation;
55+
56+
private final Function<Predicate, Mono<Long>> countOperation;
57+
58+
private final Function<Predicate, Mono<Boolean>> existsOperation;
59+
60+
ReactiveFluentQueryByPredicate(
61+
Predicate predicate,
62+
Neo4jPersistentEntity<S> metaData,
63+
Class<R> resultType,
64+
ReactiveFluentFindOperation findOperation,
65+
Function<Predicate, Mono<Long>> countOperation,
66+
Function<Predicate, Mono<Boolean>> existsOperation
67+
) {
68+
this(predicate, metaData, resultType, findOperation, countOperation, existsOperation, Sort.unsorted(), null);
69+
}
70+
71+
ReactiveFluentQueryByPredicate(
72+
Predicate predicate,
73+
Neo4jPersistentEntity<S> metaData,
74+
Class<R> resultType,
75+
ReactiveFluentFindOperation findOperation,
76+
Function<Predicate, Mono<Long>> countOperation,
77+
Function<Predicate, Mono<Boolean>> existsOperation,
78+
Sort sort,
79+
@Nullable Collection<String> properties
80+
) {
81+
super(resultType, sort, properties);
82+
this.predicate = predicate;
83+
this.metaData = metaData;
84+
this.findOperation = findOperation;
85+
this.countOperation = countOperation;
86+
this.existsOperation = existsOperation;
87+
}
88+
89+
@Override
90+
@SuppressWarnings("HiddenField")
91+
public ReactiveFluentQuery<R> sortBy(Sort sort) {
92+
93+
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, this.resultType, this.findOperation,
94+
this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
95+
}
96+
97+
@Override
98+
@SuppressWarnings("HiddenField")
99+
public <NR> ReactiveFluentQuery<NR> as(Class<NR> resultType) {
100+
101+
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
102+
this.countOperation, this.existsOperation);
103+
}
104+
105+
@Override
106+
@SuppressWarnings("HiddenField")
107+
public ReactiveFluentQuery<R> project(Collection<String> properties) {
108+
109+
return new ReactiveFluentQueryByPredicate<>(this.predicate, this.metaData, resultType, this.findOperation,
110+
this.countOperation, this.existsOperation, sort, mergeProperties(properties));
111+
}
112+
113+
@Override
114+
public Mono<R> one() {
115+
116+
return findOperation.find(metaData.getType())
117+
.as(resultType)
118+
.matching(
119+
QueryFragmentsAndParameters.forCondition(metaData,
120+
Cypher.adapt(predicate).asCondition(),
121+
null,
122+
CypherAdapterUtils.toSortItems(this.metaData, sort),
123+
createIncludedFieldsPredicate()))
124+
.one();
125+
}
126+
127+
@Override
128+
public Mono<R> first() {
129+
130+
return all().take(1).singleOrEmpty();
131+
}
132+
133+
@Override
134+
public Flux<R> all() {
135+
136+
return findOperation.find(metaData.getType())
137+
.as(resultType)
138+
.matching(
139+
QueryFragmentsAndParameters.forCondition(metaData,
140+
Cypher.adapt(predicate).asCondition(),
141+
null,
142+
CypherAdapterUtils.toSortItems(this.metaData, sort),
143+
createIncludedFieldsPredicate()))
144+
.all();
145+
}
146+
147+
@Override
148+
public Mono<Page<R>> page(Pageable pageable) {
149+
150+
Flux<R> results = findOperation.find(metaData.getType())
151+
.as(resultType)
152+
.matching(
153+
QueryFragmentsAndParameters.forCondition(metaData,
154+
Cypher.adapt(predicate).asCondition(),
155+
pageable, null,
156+
createIncludedFieldsPredicate()))
157+
.all();
158+
159+
return results.collectList().zipWith(countOperation.apply(predicate)).map(tuple -> {
160+
Page<R> page = PageableExecutionUtils.getPage(tuple.getT1(), pageable, () -> tuple.getT2());
161+
return page;
162+
});
163+
}
164+
165+
@Override
166+
public Mono<Long> count() {
167+
return countOperation.apply(predicate);
168+
}
169+
170+
@Override
171+
public Mono<Boolean> exists() {
172+
return existsOperation.apply(predicate);
173+
}
174+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
/*
2+
* Copyright 2011-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.springframework.data.neo4j.repository.query;
17+
18+
import static org.neo4j.cypherdsl.core.Cypher.asterisk;
19+
20+
import reactor.core.publisher.Flux;
21+
import reactor.core.publisher.Mono;
22+
23+
import java.util.Arrays;
24+
import java.util.Collection;
25+
import java.util.function.Function;
26+
27+
import org.apiguardian.api.API;
28+
import org.neo4j.cypherdsl.core.Condition;
29+
import org.neo4j.cypherdsl.core.Conditions;
30+
import org.neo4j.cypherdsl.core.Cypher;
31+
import org.neo4j.cypherdsl.core.Functions;
32+
import org.neo4j.cypherdsl.core.SortItem;
33+
import org.neo4j.cypherdsl.core.Statement;
34+
import org.reactivestreams.Publisher;
35+
import org.springframework.data.domain.Sort;
36+
import org.springframework.data.neo4j.core.ReactiveFluentFindOperation;
37+
import org.springframework.data.neo4j.core.ReactiveNeo4jOperations;
38+
import org.springframework.data.neo4j.core.mapping.CypherGenerator;
39+
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
40+
import org.springframework.data.neo4j.repository.support.Neo4jEntityInformation;
41+
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
42+
import org.springframework.data.repository.query.FluentQuery.ReactiveFluentQuery;
43+
44+
import com.querydsl.core.types.OrderSpecifier;
45+
import com.querydsl.core.types.Predicate;
46+
47+
/**
48+
* Querydsl specific fragment for extending {@link org.springframework.data.neo4j.repository.support.SimpleReactiveNeo4jRepository}
49+
* with an implementation of {@link ReactiveQuerydslPredicateExecutor}. Provides the necessary infrastructure for translating
50+
* Query-DSL predicates into conditions that are passed along to the Cypher-DSL and eventually to the template infrastructure.
51+
* This fragment will be loaded by the repository infrastructure when a repository is declared extending the above interface.
52+
*
53+
* @author Michael J. Simons
54+
* @param <T> The returned domain type.
55+
* @since 6.2
56+
*/
57+
@API(status = API.Status.INTERNAL, since = "6.2")
58+
public final class ReactiveQuerydslNeo4jPredicateExecutor<T> implements ReactiveQuerydslPredicateExecutor<T> {
59+
60+
private final Neo4jEntityInformation<T, Object> entityInformation;
61+
62+
private final ReactiveNeo4jOperations neo4jOperations;
63+
64+
private final Neo4jPersistentEntity<T> metaData;
65+
66+
public ReactiveQuerydslNeo4jPredicateExecutor(Neo4jEntityInformation<T, Object> entityInformation,
67+
ReactiveNeo4jOperations neo4jOperations) {
68+
69+
this.entityInformation = entityInformation;
70+
this.neo4jOperations = neo4jOperations;
71+
this.metaData = this.entityInformation.getEntityMetaData();
72+
}
73+
74+
@Override
75+
public Mono<T> findOne(Predicate predicate) {
76+
77+
return this.neo4jOperations.toExecutableQuery(
78+
this.metaData.getType(),
79+
QueryFragmentsAndParameters.forCondition(this.metaData, Cypher.adapt(predicate).asCondition(), null,
80+
null)
81+
).flatMap(ReactiveNeo4jOperations.ExecutableQuery::getSingleResult);
82+
}
83+
84+
@Override
85+
public Flux<T> findAll(Predicate predicate) {
86+
87+
return doFindAll(Cypher.adapt(predicate).asCondition(), null);
88+
}
89+
90+
@Override
91+
public Flux<T> findAll(Predicate predicate, Sort sort) {
92+
93+
return doFindAll(Cypher.adapt(predicate).asCondition(), CypherAdapterUtils.toSortItems(this.metaData, sort));
94+
}
95+
96+
@Override
97+
public Flux<T> findAll(Predicate predicate, OrderSpecifier<?>... orders) {
98+
return doFindAll(Cypher.adapt(predicate).asCondition(), Arrays.asList(QuerydslNeo4jPredicateExecutor.toSortItems(orders)));
99+
100+
}
101+
102+
@Override
103+
public Flux<T> findAll(OrderSpecifier<?>... orders) {
104+
105+
return doFindAll(Conditions.noCondition(), Arrays.asList(QuerydslNeo4jPredicateExecutor.toSortItems(orders)));
106+
}
107+
108+
private Flux<T> doFindAll(Condition condition, Collection<SortItem> sortItems) {
109+
return this.neo4jOperations.toExecutableQuery(
110+
this.metaData.getType(),
111+
QueryFragmentsAndParameters.forCondition(this.metaData, condition, null,
112+
sortItems)
113+
).flatMapMany(ReactiveNeo4jOperations.ExecutableQuery::getResults);
114+
}
115+
116+
@Override
117+
public Mono<Long> count(Predicate predicate) {
118+
119+
Statement statement = CypherGenerator.INSTANCE.prepareMatchOf(this.metaData,
120+
Cypher.adapt(predicate).asCondition())
121+
.returning(Functions.count(asterisk())).build();
122+
return this.neo4jOperations.count(statement, statement.getParameters());
123+
}
124+
125+
@Override
126+
public Mono<Boolean> exists(Predicate predicate) {
127+
return findAll(predicate).hasElements();
128+
}
129+
130+
@Override
131+
public <S extends T, R, P extends Publisher<R>> P findBy(Predicate predicate, Function<ReactiveFluentQuery<S>, P> queryFunction) {
132+
133+
if (this.neo4jOperations instanceof ReactiveFluentFindOperation) {
134+
@SuppressWarnings("unchecked") // defaultResultType will be a supertype of S and at this stage, the same.
135+
ReactiveFluentQuery<S> fluentQuery = (ReactiveFluentQuery<S>) new ReactiveFluentQueryByPredicate<>(predicate, metaData, metaData.getType(),
136+
(ReactiveFluentFindOperation) this.neo4jOperations, this::count, this::exists);
137+
return queryFunction.apply(fluentQuery);
138+
}
139+
throw new UnsupportedOperationException(
140+
"Fluent find by example not supported with standard Neo4jOperations. Must support fluent queries too.");
141+
}
142+
}

src/main/java/org/springframework/data/neo4j/repository/support/Neo4jRepositoryFactory.java

-6
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import java.util.Optional;
1919

20-
import org.springframework.dao.InvalidDataAccessApiUsageException;
2120
import org.springframework.data.neo4j.core.Neo4jOperations;
2221
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
2322
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
@@ -102,11 +101,6 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata
102101

103102
private RepositoryFragment<Object> createDSLExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
104103

105-
if (metadata.isReactiveRepository()) {
106-
throw new InvalidDataAccessApiUsageException(
107-
"Cannot combine DSL executor and reactive repository support in a single interface");
108-
}
109-
110104
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
111105
Object querydslFragment = instantiateClass(implementor, entityInformation, neo4jOperations);
112106

src/main/java/org/springframework/data/neo4j/repository/support/ReactiveNeo4jRepositoryFactory.java

+19
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@
2525
import org.springframework.data.neo4j.core.mapping.Neo4jPersistentEntity;
2626
import org.springframework.data.neo4j.repository.ReactiveNeo4jRepository;
2727
import org.springframework.data.neo4j.repository.query.ReactiveNeo4jQueryLookupStrategy;
28+
import org.springframework.data.neo4j.repository.query.ReactiveQuerydslNeo4jPredicateExecutor;
2829
import org.springframework.data.neo4j.repository.query.SimpleReactiveQueryByExampleExecutor;
30+
import org.springframework.data.querydsl.QuerydslUtils;
31+
import org.springframework.data.querydsl.ReactiveQuerydslPredicateExecutor;
2932
import org.springframework.data.repository.core.RepositoryInformation;
3033
import org.springframework.data.repository.core.RepositoryMetadata;
3134
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
@@ -80,9 +83,25 @@ protected RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata
8083

8184
fragments = fragments.append(RepositoryFragment.implemented(byExampleExecutor));
8285

86+
boolean isQueryDslRepository = QuerydslUtils.QUERY_DSL_PRESENT
87+
&& ReactiveQuerydslPredicateExecutor.class.isAssignableFrom(metadata.getRepositoryInterface());
88+
89+
if (isQueryDslRepository) {
90+
91+
fragments = fragments.append(createDSLExecutorFragment(metadata, ReactiveQuerydslNeo4jPredicateExecutor.class));
92+
}
93+
8394
return fragments;
8495
}
8596

97+
private RepositoryFragment<Object> createDSLExecutorFragment(RepositoryMetadata metadata, Class<?> implementor) {
98+
99+
Neo4jEntityInformation<?, Object> entityInformation = getEntityInformation(metadata.getDomainType());
100+
Object querydslFragment = instantiateClass(implementor, entityInformation, neo4jOperations);
101+
102+
return RepositoryFragment.implemented(querydslFragment);
103+
}
104+
86105
@Override
87106
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
88107
return SimpleReactiveNeo4jRepository.class;

0 commit comments

Comments
 (0)