Skip to content

Commit 7ad2bf7

Browse files
GH-2343 - Add FluentQuery support to QuerydslPredicateExecutor and QueryByExampleExecutor.
This change uses the existing `FluentOperations` to cater for the above usescases. It introduces a new `matching` operation for taking in a `QueryFragmentsAndParameters` on those operations. That operation is of limited external use, but necessary to implement that feature without adding more overhead than necessary. This closes #2343.
1 parent 2c7b9ec commit 7ad2bf7

18 files changed

+1179
-52
lines changed

Diff for: src/main/java/org/springframework/data/neo4j/core/FluentFindOperation.java

+11
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222

2323
import org.apiguardian.api.API;
2424
import org.neo4j.cypherdsl.core.Statement;
25+
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
2526
import org.springframework.lang.Nullable;
2627

2728
/**
@@ -106,6 +107,16 @@ interface FindWithQuery<T> extends TerminatingFindWithoutQuery<T> {
106107
*/
107108
TerminatingFind<T> matching(String query, @Nullable Map<String, Object> parameter);
108109

110+
/**
111+
* Creates an executable query based on fragments and parameters. Hardly useful outside framework-code
112+
* and we actively discourage using this method.
113+
*
114+
* @param queryFragmentsAndParameters Encapsulated query fragements and parameters as created by the repository abstraction.
115+
* @return new instance of {@link TerminatingFind}.
116+
* @throws IllegalArgumentException if queryFragmentsAndParameters is {@literal null}.
117+
*/
118+
TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters);
119+
109120
/**
110121
* Set the filter query to be used.
111122
*

Diff for: src/main/java/org/springframework/data/neo4j/core/FluentOperationSupport.java

+22-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.util.List;
2020
import java.util.Map;
2121

22+
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
2223
import org.springframework.util.Assert;
2324

2425
/**
@@ -51,6 +52,7 @@ private static class ExecutableFindSupport<T>
5152
private final Class<T> returnType;
5253
private final String query;
5354
private final Map<String, Object> parameters;
55+
private final QueryFragmentsAndParameters queryFragmentsAndParameters;
5456

5557
ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, String query,
5658
Map<String, Object> parameters) {
@@ -59,6 +61,16 @@ private static class ExecutableFindSupport<T>
5961
this.returnType = returnType;
6062
this.query = query;
6163
this.parameters = parameters;
64+
this.queryFragmentsAndParameters = null;
65+
}
66+
67+
ExecutableFindSupport(Neo4jTemplate template, Class<?> domainType, Class<T> returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
68+
this.template = template;
69+
this.domainType = domainType;
70+
this.returnType = returnType;
71+
this.query = null;
72+
this.parameters = null;
73+
this.queryFragmentsAndParameters = queryFragmentsAndParameters;
6274
}
6375

6476
@Override
@@ -79,6 +91,15 @@ public TerminatingFind<T> matching(String query, Map<String, Object> parameters)
7991
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
8092
}
8193

94+
@Override
95+
@SuppressWarnings("HiddenField")
96+
public TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters) {
97+
98+
Assert.notNull(queryFragmentsAndParameters, "Query fragments must not be null!");
99+
100+
return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
101+
}
102+
82103
@Override
83104
public T oneValue() {
84105

@@ -95,7 +116,7 @@ public List<T> all() {
95116
}
96117

97118
private List<T> doFind(TemplateSupport.FetchType fetchType) {
98-
return template.doFind(query, parameters, domainType, returnType, fetchType);
119+
return template.doFind(query, parameters, domainType, returnType, fetchType, queryFragmentsAndParameters);
99120
}
100121
}
101122

Diff for: src/main/java/org/springframework/data/neo4j/core/Neo4jTemplate.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -245,14 +245,19 @@ public <T> ExecutableFind<T> find(Class<T> domainType) {
245245
}
246246

247247
@SuppressWarnings("unchecked")
248-
<T, R> List<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
248+
<T, R> List<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {
249249

250250
List<T> intermediaResults = Collections.emptyList();
251-
if (cypherQuery == null && fetchType == TemplateSupport.FetchType.ALL) {
251+
if (cypherQuery == null && queryFragmentsAndParameters == null && fetchType == TemplateSupport.FetchType.ALL) {
252252
intermediaResults = doFindAll(domainType, resultType);
253253
} else {
254-
ExecutableQuery<T> executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
255-
parameters == null ? Collections.emptyMap() : parameters);
254+
ExecutableQuery<T> executableQuery;
255+
if (queryFragmentsAndParameters == null) {
256+
executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
257+
parameters == null ? Collections.emptyMap() : parameters);
258+
} else {
259+
executableQuery = createExecutableQuery(domainType, resultType, queryFragmentsAndParameters);
260+
}
256261
switch (fetchType) {
257262
case ALL:
258263
intermediaResults = executableQuery.getResults();

Diff for: src/main/java/org/springframework/data/neo4j/core/ReactiveFluentFindOperation.java

+11
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import org.apiguardian.api.API;
2525
import org.neo4j.cypherdsl.core.Statement;
26+
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
2627
import org.springframework.lang.Nullable;
2728

2829
/**
@@ -96,6 +97,16 @@ interface FindWithQuery<T> extends TerminatingFindWithoutQuery<T> {
9697
*/
9798
TerminatingFind<T> matching(String query, @Nullable Map<String, Object> parameter);
9899

100+
/**
101+
* Creates an executable query based on fragments and parameters. Hardly useful outside framework-code
102+
* and we actively discourage using this method.
103+
*
104+
* @param queryFragmentsAndParameters Encapsulated query fragements and parameters as created by the repository abstraction.
105+
* @return new instance of {@link FluentFindOperation.TerminatingFind}.
106+
* @throws IllegalArgumentException if queryFragmentsAndParameters is {@literal null}.
107+
*/
108+
TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters);
109+
99110
/**
100111
* Set the filter query to be used.
101112
*

Diff for: src/main/java/org/springframework/data/neo4j/core/ReactiveFluentOperationSupport.java

+20-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.Collections;
2222
import java.util.Map;
2323

24+
import org.springframework.data.neo4j.repository.query.QueryFragmentsAndParameters;
2425
import org.springframework.util.Assert;
2526

2627
/**
@@ -54,6 +55,7 @@ private static class ExecutableFindSupport<T>
5455
private final Class<T> returnType;
5556
private final String query;
5657
private final Map<String, Object> parameters;
58+
private final QueryFragmentsAndParameters queryFragmentsAndParameters;
5759

5860
ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, String query,
5961
Map<String, Object> parameters) {
@@ -62,6 +64,16 @@ private static class ExecutableFindSupport<T>
6264
this.returnType = returnType;
6365
this.query = query;
6466
this.parameters = parameters;
67+
this.queryFragmentsAndParameters = null;
68+
}
69+
70+
ExecutableFindSupport(ReactiveNeo4jTemplate template, Class<?> domainType, Class<T> returnType, QueryFragmentsAndParameters queryFragmentsAndParameters) {
71+
this.template = template;
72+
this.domainType = domainType;
73+
this.returnType = returnType;
74+
this.query = null;
75+
this.parameters = null;
76+
this.queryFragmentsAndParameters = queryFragmentsAndParameters;
6577
}
6678

6779
@Override
@@ -82,6 +94,13 @@ public TerminatingFind<T> matching(String query, Map<String, Object> parameters)
8294
return new ExecutableFindSupport<>(template, domainType, returnType, query, parameters);
8395
}
8496

97+
@Override
98+
@SuppressWarnings("HiddenField")
99+
public TerminatingFind<T> matching(QueryFragmentsAndParameters queryFragmentsAndParameters) {
100+
101+
return new ExecutableFindSupport<>(template, domainType, returnType, queryFragmentsAndParameters);
102+
}
103+
85104
@Override
86105
public Mono<T> one() {
87106
return doFind(TemplateSupport.FetchType.ONE).single();
@@ -93,7 +112,7 @@ public Flux<T> all() {
93112
}
94113

95114
private Flux<T> doFind(TemplateSupport.FetchType fetchType) {
96-
return template.doFind(query, parameters, domainType, returnType, fetchType);
115+
return template.doFind(query, parameters, domainType, returnType, fetchType, queryFragmentsAndParameters);
97116
}
98117
}
99118

Diff for: src/main/java/org/springframework/data/neo4j/core/ReactiveNeo4jTemplate.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,19 @@ public <T> ExecutableFind<T> find(Class<T> domainType) {
225225
}
226226

227227
@SuppressWarnings("unchecked")
228-
<T, R> Flux<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType) {
228+
<T, R> Flux<R> doFind(@Nullable String cypherQuery, @Nullable Map<String, Object> parameters, Class<T> domainType, Class<R> resultType, TemplateSupport.FetchType fetchType, @Nullable QueryFragmentsAndParameters queryFragmentsAndParameters) {
229229

230230
Flux<T> intermediaResults = null;
231-
if (cypherQuery == null && fetchType == TemplateSupport.FetchType.ALL) {
231+
if (cypherQuery == null && queryFragmentsAndParameters == null && fetchType == TemplateSupport.FetchType.ALL) {
232232
intermediaResults = doFindAll(domainType, resultType);
233233
} else {
234-
Mono<ExecutableQuery<T>> executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
235-
parameters == null ? Collections.emptyMap() : parameters);
234+
Mono<ExecutableQuery<T>> executableQuery;
235+
if (queryFragmentsAndParameters == null) {
236+
executableQuery = createExecutableQuery(domainType, resultType, cypherQuery,
237+
parameters == null ? Collections.emptyMap() : parameters);
238+
} else {
239+
executableQuery = createExecutableQuery(domainType, resultType, queryFragmentsAndParameters);
240+
}
236241

237242
switch (fetchType) {
238243
case ALL:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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 java.util.Collection;
19+
import java.util.List;
20+
import java.util.function.Function;
21+
import java.util.function.LongSupplier;
22+
import java.util.stream.Stream;
23+
24+
import org.apiguardian.api.API;
25+
import org.springframework.data.domain.Example;
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.FluentFindOperation;
30+
import org.springframework.data.neo4j.core.mapping.Neo4jMappingContext;
31+
import org.springframework.data.repository.query.FluentQuery.FetchableFluentQuery;
32+
import org.springframework.data.support.PageableExecutionUtils;
33+
import org.springframework.lang.Nullable;
34+
35+
/**
36+
* Immutable implementation of a {@link FetchableFluentQuery}. All
37+
* methods that return a {@link FetchableFluentQuery} return a new instance, the original instance won't be
38+
* modified.
39+
*
40+
* @author Michael J. Simons
41+
* @param <S> Source type
42+
* @param <R> Result type
43+
* @since 6.2
44+
*/
45+
@API(status = API.Status.INTERNAL, since = "6.2")
46+
final class FetchableFluentQueryByExample<S, R> extends FluentQuerySupport<R> implements FetchableFluentQuery<R> {
47+
48+
private final Neo4jMappingContext mappingContext;
49+
50+
private final Example<S> example;
51+
52+
private final FluentFindOperation findOperation;
53+
54+
private final Function<Example<S>, Long> countOperation;
55+
56+
private final Function<Example<S>, Boolean> existsOperation;
57+
58+
FetchableFluentQueryByExample(
59+
Example<S> example,
60+
Class<R> resultType,
61+
Neo4jMappingContext mappingContext,
62+
FluentFindOperation findOperation,
63+
Function<Example<S>, Long> countOperation,
64+
Function<Example<S>, Boolean> existsOperation
65+
) {
66+
this(example, resultType, mappingContext, findOperation, countOperation, existsOperation, Sort.unsorted(),
67+
null);
68+
}
69+
70+
FetchableFluentQueryByExample(
71+
Example<S> example,
72+
Class<R> resultType,
73+
Neo4jMappingContext mappingContext,
74+
FluentFindOperation findOperation,
75+
Function<Example<S>, Long> countOperation,
76+
Function<Example<S>, Boolean> existsOperation,
77+
Sort sort,
78+
@Nullable Collection<String> properties
79+
) {
80+
super(resultType, sort, properties);
81+
this.mappingContext = mappingContext;
82+
this.example = example;
83+
this.findOperation = findOperation;
84+
this.countOperation = countOperation;
85+
this.existsOperation = existsOperation;
86+
}
87+
88+
@Override
89+
@SuppressWarnings("HiddenField")
90+
public FetchableFluentQuery<R> sortBy(Sort sort) {
91+
92+
return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
93+
this.countOperation, this.existsOperation, this.sort.and(sort), this.properties);
94+
}
95+
96+
@Override
97+
@SuppressWarnings("HiddenField")
98+
public <NR> FetchableFluentQuery<NR> as(Class<NR> resultType) {
99+
100+
return new FetchableFluentQueryByExample<>(this.example, resultType, this.mappingContext, this.findOperation,
101+
this.countOperation, this.existsOperation);
102+
}
103+
104+
@Override
105+
@SuppressWarnings("HiddenField")
106+
public FetchableFluentQuery<R> project(Collection<String> properties) {
107+
108+
return new FetchableFluentQueryByExample<>(this.example, this.resultType, this.mappingContext, this.findOperation,
109+
this.countOperation, this.existsOperation, this.sort, mergeProperties(properties));
110+
}
111+
112+
@Override
113+
public R one() {
114+
115+
return findOperation.find(example.getProbeType())
116+
.as(resultType)
117+
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
118+
createIncludedFieldsPredicate()))
119+
.oneValue();
120+
}
121+
122+
@Override
123+
public R first() {
124+
125+
List<R> all = all();
126+
return all.isEmpty() ? null : all.get(0);
127+
}
128+
129+
@Override
130+
public List<R> all() {
131+
132+
return findOperation.find(example.getProbeType())
133+
.as(resultType)
134+
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, sort,
135+
createIncludedFieldsPredicate()))
136+
.all();
137+
}
138+
139+
@Override
140+
public Page<R> page(Pageable pageable) {
141+
142+
List<R> page = findOperation.find(example.getProbeType())
143+
.as(resultType)
144+
.matching(QueryFragmentsAndParameters.forExample(mappingContext, example, pageable,
145+
createIncludedFieldsPredicate()))
146+
.all();
147+
148+
LongSupplier totalCountSupplier = this::count;
149+
return PageableExecutionUtils.getPage(page, pageable, totalCountSupplier);
150+
}
151+
152+
@Override
153+
public Stream<R> stream() {
154+
return all().stream();
155+
}
156+
157+
@Override
158+
public long count() {
159+
return countOperation.apply(example);
160+
}
161+
162+
@Override
163+
public boolean exists() {
164+
return existsOperation.apply(example);
165+
}
166+
}

0 commit comments

Comments
 (0)