Support collection parameters in @Query methods.

Original Pull Request #1856 
Closes #1858
This commit is contained in:
Niklas Herder 2021-07-03 13:42:56 +02:00 committed by GitHub
parent 112ca59c95
commit 6f84a1c589
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 87 additions and 5 deletions

View File

@ -15,8 +15,10 @@
*/ */
package org.springframework.data.elasticsearch.repository.support; package org.springframework.data.elasticsearch.repository.support;
import java.util.Collection;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.springframework.core.convert.support.GenericConversionService; import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.repository.query.ParameterAccessor; import org.springframework.data.repository.query.ParameterAccessor;
@ -24,6 +26,7 @@ import org.springframework.util.NumberUtils;
/** /**
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Niklas Herder
*/ */
final public class StringQueryUtil { final public class StringQueryUtil {
@ -53,6 +56,28 @@ final public class StringQueryUtil {
// noinspection ConstantConditions // noinspection ConstantConditions
if (parameter != null) { if (parameter != null) {
parameterValue = convert(parameter);
}
return parameterValue;
}
private static String convert(Object parameter) {
if (Collection.class.isAssignableFrom(parameter.getClass())) {
Collection<?> collectionParam = (Collection<?>) parameter;
StringBuilder sb = new StringBuilder("[");
sb.append(collectionParam.stream().map(o -> {
if (o instanceof String) {
return "\"" + convert(o) + "\"";
} else {
return convert(o);
}
}).collect(Collectors.joining(",")));
sb.append("]");
return sb.toString();
} else {
String parameterValue = "null";
if (conversionService.canConvert(parameter.getClass(), String.class)) { if (conversionService.canConvert(parameter.getClass(), String.class)) {
String converted = conversionService.convert(parameter, String.class); String converted = conversionService.convert(parameter, String.class);
@ -62,11 +87,10 @@ final public class StringQueryUtil {
} else { } else {
parameterValue = parameter.toString(); parameterValue = parameter.toString();
} }
parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\""));
return parameterValue;
} }
parameterValue = parameterValue.replaceAll("\"", Matcher.quoteReplacement("\\\""));
return parameterValue;
} }
} }

View File

@ -18,6 +18,7 @@ package org.springframework.data.elasticsearch.repository.query;
import static org.assertj.core.api.Assertions.*; import static org.assertj.core.api.Assertions.*;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap; import java.util.HashMap;
@ -51,6 +52,7 @@ import org.springframework.lang.Nullable;
/** /**
* @author Christoph Strobl * @author Christoph Strobl
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Niklas Herder
*/ */
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class ElasticsearchStringQueryUnitTests { public class ElasticsearchStringQueryUnitTests {
@ -95,7 +97,42 @@ public class ElasticsearchStringQueryUnitTests {
.isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}"); .isEqualTo("{\"bool\":{\"must\": [{\"match\": {\"prefix\": {\"name\" : \"hello \\\"Stranger\\\"\"}}]}}");
} }
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, String... args) @Test // #1858
@DisplayName("should only quote String query parameters")
void shouldOnlyEscapeStringQueryParameters() throws Exception {
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByAge", Integer.valueOf(30));
assertThat(query).isInstanceOf(StringQuery.class);
assertThat(((StringQuery) query).getSource()).isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'age' : 30 } } } }");
}
@Test // #1858
@DisplayName("should only quote String collection query parameters")
void shouldOnlyEscapeStringCollectionQueryParameters() throws Exception {
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByAgeIn",
new ArrayList<>(Arrays.asList(30, 35, 40)));
assertThat(query).isInstanceOf(StringQuery.class);
assertThat(((StringQuery) query).getSource())
.isEqualTo("{ 'bool' : { 'must' : { 'term' : { 'age' : [30,35,40] } } } }");
}
@Test // #1858
@DisplayName("should escape Strings in collection query parameters")
void shouldEscapeStringsInCollectionsQueryParameters() throws Exception {
final List<String> another_string = Arrays.asList("hello \"Stranger\"", "Another string");
List<String> params = new ArrayList<>(another_string);
org.springframework.data.elasticsearch.core.query.Query query = createQuery("findByNameIn", params);
assertThat(query).isInstanceOf(StringQuery.class);
assertThat(((StringQuery) query).getSource()).isEqualTo(
"{ 'bool' : { 'must' : { 'terms' : { 'name' : [\"hello \\\"Stranger\\\"\",\"Another string\"] } } } }");
}
private org.springframework.data.elasticsearch.core.query.Query createQuery(String methodName, Object... args)
throws NoSuchMethodException { throws NoSuchMethodException {
Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new); Class<?>[] argTypes = Arrays.stream(args).map(Object::getClass).toArray(Class[]::new);
@ -103,6 +140,7 @@ public class ElasticsearchStringQueryUnitTests {
ElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod); ElasticsearchStringQuery elasticsearchStringQuery = queryForMethod(queryMethod);
return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args)); return elasticsearchStringQuery.createQuery(new ElasticsearchParametersParameterAccessor(queryMethod, args));
} }
private ElasticsearchStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) { private ElasticsearchStringQuery queryForMethod(ElasticsearchQueryMethod queryMethod) {
return new ElasticsearchStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery()); return new ElasticsearchStringQuery(queryMethod, operations, queryMethod.getAnnotatedQuery());
} }
@ -116,9 +154,18 @@ public class ElasticsearchStringQueryUnitTests {
private interface SampleRepository extends Repository<Person, String> { private interface SampleRepository extends Repository<Person, String> {
@Query("{ 'bool' : { 'must' : { 'term' : { 'age' : ?0 } } } }")
List<Person> findByAge(Integer age);
@Query("{ 'bool' : { 'must' : { 'term' : { 'age' : ?0 } } } }")
List<Person> findByAgeIn(ArrayList<Integer> age);
@Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }") @Query("{ 'bool' : { 'must' : { 'term' : { 'name' : '?0' } } } }")
Person findByName(String name); Person findByName(String name);
@Query("{ 'bool' : { 'must' : { 'terms' : { 'name' : ?0 } } } }")
Person findByNameIn(ArrayList<String> names);
@Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)") @Query(value = "name:(?0, ?11, ?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9, ?10, ?11, ?0, ?1)")
Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5, Person findWithRepeatedPlaceholder(String arg0, String arg1, String arg2, String arg3, String arg4, String arg5,
String arg6, String arg7, String arg8, String arg9, String arg10, String arg11); String arg6, String arg7, String arg8, String arg9, String arg10, String arg11);
@ -131,16 +178,27 @@ public class ElasticsearchStringQueryUnitTests {
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
* @author Artur Konczak * @author Artur Konczak
* @author Niklas Herder
*/ */
@Document(indexName = "test-index-person-query-unittest") @Document(indexName = "test-index-person-query-unittest")
static class Person { static class Person {
@Nullable public int age;
@Nullable @Id private String id; @Nullable @Id private String id;
@Nullable private String name; @Nullable private String name;
@Nullable @Field(type = FieldType.Nested) private List<Car> car; @Nullable @Field(type = FieldType.Nested) private List<Car> car;
@Nullable @Field(type = FieldType.Nested, includeInParent = true) private List<Book> books; @Nullable @Field(type = FieldType.Nested, includeInParent = true) private List<Book> books;
@Nullable
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Nullable @Nullable
public String getId() { public String getId() {
return id; return id;