Skip to content

Commit cd3d2cc

Browse files
committed
wip: handle named parameters
1 parent 0a0fc75 commit cd3d2cc

13 files changed

+120
-20
lines changed

src/main/java/org/springframework/data/elasticsearch/annotations/Query.java

+7
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,11 @@
5252
* @since 4.2
5353
*/
5454
boolean count() default false;
55+
56+
/**
57+
* Returns whether the query defined should be built using named parameters instead of positional parameters.
58+
*
59+
* @return false by default
60+
*/
61+
boolean useNamedParameters() default false;
5562
}

src/main/java/org/springframework/data/elasticsearch/repository/query/AbstractReactiveElasticsearchRepositoryQuery.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ private Object execute(ElasticsearchParameterAccessor parameterAccessor) {
8383
ResultProcessor processor = queryMethod.getResultProcessor().withDynamicProjection(parameterAccessor);
8484

8585
Query query = createQuery(
86-
new ConvertingParameterAccessor(elasticsearchOperations.getElasticsearchConverter(), parameterAccessor));
86+
new ConvertingParameterAccessor(elasticsearchOperations.getElasticsearchConverter(), parameterAccessor),
87+
queryMethod.hasNamedParameters());
8788

8889
if (queryMethod.hasAnnotatedHighlight()) {
8990
query.setHighlightQuery(queryMethod.getAnnotatedHighlightQuery());
@@ -108,9 +109,10 @@ private ReactiveElasticsearchQueryExecution getExecution(ElasticsearchParameterA
108109
* Creates a {@link Query} instance using the given {@link ParameterAccessor}
109110
*
110111
* @param accessor must not be {@literal null}.
112+
* @param useNamedParameters whether to use named parameters or not
111113
* @return
112114
*/
113-
protected abstract Query createQuery(ElasticsearchParameterAccessor accessor);
115+
protected abstract Query createQuery(ElasticsearchParameterAccessor accessor, boolean useNamedParameters);
114116

115117
private ReactiveElasticsearchQueryExecution getExecutionToWrap(ElasticsearchParameterAccessor accessor,
116118
ReactiveElasticsearchOperations operations) {

src/main/java/org/springframework/data/elasticsearch/repository/query/ConvertingParameterAccessor.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@
1616
package org.springframework.data.elasticsearch.repository.query;
1717

1818
import java.util.Iterator;
19-
import java.util.Optional;
2019

2120
import org.springframework.data.domain.Pageable;
2221
import org.springframework.data.domain.Sort;
2322
import org.springframework.data.elasticsearch.core.convert.ElasticsearchConverter;
23+
import org.springframework.data.repository.query.Parameter;
24+
import org.springframework.data.repository.query.Parameters;
2425
import org.springframework.lang.Nullable;
2526

2627
/**
@@ -39,6 +40,16 @@ public ConvertingParameterAccessor(ElasticsearchConverter converter, Elasticsear
3940
this.delegate = delegate;
4041
}
4142

43+
@Override
44+
public Parameters<?, ?> getParameters() {
45+
return delegate.getParameters();
46+
}
47+
48+
@Override
49+
public <T> T getValue(Parameter parameter) {
50+
return delegate.getValue(parameter);
51+
}
52+
4253
@Override
4354
public Object[] getValues() {
4455
return delegate.getValues();

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParameterAccessor.java

+8
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,22 @@
1515
*/
1616
package org.springframework.data.elasticsearch.repository.query;
1717

18+
import org.springframework.data.repository.query.Parameter;
1819
import org.springframework.data.repository.query.ParameterAccessor;
20+
import org.springframework.data.repository.query.Parameters;
21+
import org.springframework.lang.Nullable;
1922

2023
/**
2124
* @author Christoph Strobl
2225
* @since 3.2
2326
*/
2427
public interface ElasticsearchParameterAccessor extends ParameterAccessor {
2528

29+
Parameters<?, ?> getParameters();
30+
31+
@Nullable
32+
<T> T getValue(Parameter parameter);
33+
2634
/**
2735
* Returns the raw parameter values of the underlying query method.
2836
*

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchParametersParameterAccessor.java

+8
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@
1818
import java.util.Arrays;
1919
import java.util.List;
2020

21+
import org.springframework.data.repository.query.Parameter;
2122
import org.springframework.data.repository.query.ParametersParameterAccessor;
23+
import org.springframework.lang.Nullable;
2224

2325
/**
2426
* @author Christoph Strobl
@@ -41,6 +43,12 @@ class ElasticsearchParametersParameterAccessor extends ParametersParameterAccess
4143
this.values = Arrays.asList(values);
4244
}
4345

46+
@Nullable
47+
@Override
48+
public <T> T getValue(Parameter parameter) {
49+
return super.getValue(parameter.getIndex());
50+
}
51+
4452
@Override
4553
public Object[] getValues() {
4654
return values.toArray();

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchQueryMethod.java

+6
Original file line numberDiff line numberDiff line change
@@ -247,4 +247,10 @@ public boolean hasCountQueryAnnotation() {
247247
return queryAnnotation != null && queryAnnotation.count();
248248
}
249249

250+
/**
251+
* @return {@literal true} if the method is annotated with {@link Query}(useNamedParameters = true)
252+
*/
253+
public boolean hasNamedParameters() {
254+
return queryAnnotation != null && queryAnnotation.useNamedParameters();
255+
}
250256
}

src/main/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQuery.java

+7-6
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
2424
import org.springframework.data.elasticsearch.core.query.StringQuery;
2525
import org.springframework.data.elasticsearch.repository.support.StringQueryUtil;
26-
import org.springframework.data.repository.query.ParametersParameterAccessor;
2726
import org.springframework.data.util.StreamUtils;
2827
import org.springframework.util.Assert;
2928

@@ -56,9 +55,10 @@ public boolean isCountQuery() {
5655
public Object execute(Object[] parameters) {
5756

5857
Class<?> clazz = queryMethod.getResultProcessor().getReturnedType().getDomainType();
59-
ParametersParameterAccessor accessor = new ParametersParameterAccessor(queryMethod.getParameters(), parameters);
58+
ElasticsearchParametersParameterAccessor accessor = new ElasticsearchParametersParameterAccessor(queryMethod,
59+
parameters);
6060

61-
StringQuery stringQuery = createQuery(accessor);
61+
StringQuery stringQuery = createQuery(accessor, queryMethod.hasNamedParameters());
6262

6363
Assert.notNull(stringQuery, "unsupported query");
6464

@@ -97,9 +97,10 @@ public Object execute(Object[] parameters) {
9797
: result;
9898
}
9999

100-
protected StringQuery createQuery(ParametersParameterAccessor parameterAccessor) {
101-
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService())
102-
.replacePlaceholders(this.query, parameterAccessor);
100+
protected StringQuery createQuery(ElasticsearchParametersParameterAccessor parameterAccessor,
101+
boolean useNamedParameters) {
102+
String queryString = new StringQueryUtil(elasticsearchOperations.getElasticsearchConverter().getConversionService(),
103+
useNamedParameters).replacePlaceholders(this.query, parameterAccessor);
103104
return new StringQuery(queryString);
104105
}
105106

src/main/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQuery.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -46,10 +46,10 @@ public ReactiveElasticsearchStringQuery(String query, ReactiveElasticsearchQuery
4646
}
4747

4848
@Override
49-
protected StringQuery createQuery(ElasticsearchParameterAccessor parameterAccessor) {
49+
protected StringQuery createQuery(ElasticsearchParameterAccessor parameterAccessor, boolean useNamedParameters) {
5050
String queryString = new StringQueryUtil(
51-
getElasticsearchOperations().getElasticsearchConverter().getConversionService()).replacePlaceholders(this.query,
52-
parameterAccessor);
51+
getElasticsearchOperations().getElasticsearchConverter().getConversionService(), useNamedParameters)
52+
.replacePlaceholders(this.query, parameterAccessor);
5353
return new StringQuery(queryString);
5454
}
5555

src/main/java/org/springframework/data/elasticsearch/repository/query/ReactivePartTreeElasticsearchQuery.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public ReactivePartTreeElasticsearchQuery(ReactiveElasticsearchQueryMethod query
4040
}
4141

4242
@Override
43-
protected Query createQuery(ElasticsearchParameterAccessor accessor) {
43+
protected Query createQuery(ElasticsearchParameterAccessor accessor, boolean useNamedParameters) {
4444
CriteriaQuery query = new ElasticsearchQueryCreator(tree, accessor, getMappingContext()).createQuery();
4545

4646
if (tree.isLimiting()) {

src/main/java/org/springframework/data/elasticsearch/repository/support/StringQueryUtil.java

+17-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import java.util.stream.Collectors;
2222

2323
import org.springframework.core.convert.ConversionService;
24+
import org.springframework.data.elasticsearch.repository.query.ElasticsearchParameterAccessor;
2425
import org.springframework.data.repository.query.ParameterAccessor;
2526
import org.springframework.util.NumberUtils;
2627

@@ -34,11 +35,17 @@ final public class StringQueryUtil {
3435

3536
private final ConversionService conversionService;
3637

37-
public StringQueryUtil(ConversionService conversionService) {
38+
private final boolean useNamedParameters;
39+
40+
public StringQueryUtil(ConversionService conversionService, boolean useNamedParameters) {
3841
this.conversionService = conversionService;
42+
this.useNamedParameters = useNamedParameters;
3943
}
4044

41-
public String replacePlaceholders(String input, ParameterAccessor accessor) {
45+
public String replacePlaceholders(String input, ElasticsearchParameterAccessor accessor) {
46+
if (useNamedParameters) {
47+
return replaceNamedParameters(input, accessor);
48+
}
4249

4350
Matcher matcher = PARAMETER_PLACEHOLDER.matcher(input);
4451
String result = input;
@@ -51,6 +58,14 @@ public String replacePlaceholders(String input, ParameterAccessor accessor) {
5158
return result;
5259
}
5360

61+
private String replaceNamedParameters(String input, ElasticsearchParameterAccessor accessor) {
62+
63+
final String[] result = { input };
64+
accessor.getParameters().forEach(parameter -> result[0] = result[0]
65+
.replaceAll(Pattern.quote(parameter.getPlaceholder()), getParameterWithIndex(accessor, parameter.getIndex())));
66+
return result[0];
67+
}
68+
5469
private String getParameterWithIndex(ParameterAccessor accessor, int index) {
5570

5671
Object parameter = accessor.getBindableValue(index);

src/test/java/org/springframework/data/elasticsearch/repository/query/ElasticsearchStringQueryUnitTests.java

+16-1
Original file line numberDiff line numberDiff line change
@@ -129,13 +129,25 @@ void shouldEscapeStringsInCollectionsQueryParameters() throws Exception {
129129
"{ 'bool' : { 'must' : { 'terms' : { 'name' : [\"hello \\\"Stranger\\\"\",\"Another string\"] } } } }");
130130
}
131131

132+
@Test
133+
@DisplayName("should replace named parameters correctly")
134+
void shouldReplaceNamedParametersCorrectly() throws Exception {
135+
136+
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findWithNamedParameters", "one", "two",
137+
"three");
138+
139+
assertThat(query).isInstanceOf(StringQuery.class);
140+
assertThat(((StringQuery) query).getSource()).isEqualTo("name:(three, two, one)");
141+
}
142+
132143
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args)
133144
throws NoSuchMethodException {
134145

135146
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
136147
ElasticsearchQueryMethod queryMethod = getQueryMethod(methodName, argTypes);
137148
ElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
138-
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
149+
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args),
150+
queryMethod.hasNamedParameters());
139151
}
140152

141153
@Test // #1866
@@ -187,6 +199,9 @@ Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String
187199

188200
@Query("{ 'bool' : { 'must' : { 'term' : { 'car' : '?0' } } } }")
189201
Person findByCar(Car car);
202+
203+
@Query(value = "name:(:arg3, :arg2, :arg1)", useNamedParameters = true)
204+
Person findWithNamedParameters(String arg1, String arg2, String arg3);
190205
}
191206

192207
/**

src/test/java/org/springframework/data/elasticsearch/repository/query/ReactiveElasticsearchStringQueryUnitTests.java

+19-3
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,8 @@ public void bindsSimplePropertyCorrectly() throws Exception {
7474
ReactiveElasticsearchStringQuery elasticsearchStringQuery = createQueryForMethod("findByName", String.class);
7575
StubParameterAccessor accessor = new StubParameterAccessor("Luke");
7676

77-
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accessor);
77+
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accessor,
78+
false);
7879
StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }");
7980

8081
assertThat(query).isInstanceOf(StringQuery.class);
@@ -89,7 +90,8 @@ public void bindsExpressionPropertyCorrectly() throws Exception {
8990
String.class);
9091
StubParameterAccessor accessor = new StubParameterAccessor("Luke");
9192

92-
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accessor);
93+
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accessor,
94+
false);
9395
StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }");
9496

9597
assertThat(query).isInstanceOf(StringQuery.class);
@@ -144,14 +146,26 @@ void shouldUseConverterOnParameters() throws Exception {
144146
.isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'car' : 'Toyota-Prius' } } } }");
145147
}
146148

149+
@Test
150+
@DisplayName("should replace named parameters correctly")
151+
void shouldReplaceNamedParametersCorrectly() throws Exception {
152+
153+
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findWithNamedParameters", "one", "two",
154+
"three");
155+
156+
assertThat(query).isInstanceOf(StringQuery.class);
157+
assertThat(((StringQuery) query).getSource()).isEqualTo("name:(three, two, one)");
158+
}
159+
147160
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args)
148161
throws NoSuchMethodException {
149162

150163
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
151164
ReactiveElasticsearchQueryMethod queryMethod = getQueryMethod(methodName, argTypes);
152165
ReactiveElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
153166

154-
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
167+
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args),
168+
queryMethod.hasNamedParameters());
155169
}
156170

157171
private ReactiveElasticsearchStringQuery queryForMethod(ReactiveElasticsearchQueryMethod queryMethod) {
@@ -195,6 +209,8 @@ Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String
195209
@Query("{ 'bool' : { 'must' : { 'term' : { 'car' : '?0' } } } }")
196210
Mono<Person> findByCar(Car car);
197211

212+
@Query(value = "name:(:arg3, :arg2, :arg1)", useNamedParameters = true)
213+
Person findWithNamedParameters(String arg1, String arg2, String arg3);
198214
}
199215

200216
/**

src/test/java/org/springframework/data/elasticsearch/repository/query/StubParameterAccessor.java

+12-1
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,12 @@
1717

1818
import java.util.Arrays;
1919
import java.util.Iterator;
20-
import java.util.Optional;
2120

2221
import org.springframework.data.domain.Pageable;
2322
import org.springframework.data.domain.Sort;
23+
import org.springframework.data.repository.query.Parameter;
2424
import org.springframework.data.repository.query.ParameterAccessor;
25+
import org.springframework.data.repository.query.Parameters;
2526

2627
/**
2728
* Simple {@link ParameterAccessor} that returns the given parameters unfiltered.
@@ -82,6 +83,16 @@ public Iterator<Object> iterator() {
8283
return Arrays.asList(values).iterator();
8384
}
8485

86+
@Override
87+
public Parameters<?, ?> getParameters() {
88+
throw new UnsupportedOperationException("Method getParameters not supported for StubParameterAccessor");
89+
}
90+
91+
@Override
92+
public <T> T getValue(Parameter parameter) {
93+
return (T) this.values[parameter.getIndex()];
94+
}
95+
8596
/*
8697
* (non-Javadoc)
8798
* @see org.springframework.data.elasticsearch.repository.query.ElasticsearchParameterAccessor#getValues()

0 commit comments

Comments
 (0)