DATAES-937 - Repository queries with IN filters fail with empty input list.

Original PR: #525

(cherry picked from commit 7117e5d70d7b52aa689787b2201a3f26639bbcf8)
This commit is contained in:
Peter-Josef Meisch 2020-09-25 09:23:35 +02:00
parent 6bd96dc4d8
commit ca6ef58195
No known key found for this signature in database
GPG Key ID: DE108246970C7708
4 changed files with 100 additions and 68 deletions

View File

@ -15,7 +15,7 @@
*/
package org.springframework.data.elasticsearch.core;
import static org.elasticsearch.index.query.Operator.AND;
import static org.elasticsearch.index.query.Operator.*;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.springframework.data.elasticsearch.core.query.Criteria.*;
@ -25,7 +25,9 @@ import java.util.List;
import java.util.ListIterator;
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
import org.elasticsearch.index.query.*;
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.util.Assert;
@ -39,7 +41,6 @@ import org.springframework.util.Assert;
*/
class CriteriaQueryProcessor {
QueryBuilder createQueryFromCriteria(Criteria criteria) {
if (criteria == null)
return null;
@ -104,7 +105,6 @@ class CriteriaQueryProcessor {
return query;
}
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) {
if (chainedCriteria.getQueryCriteriaEntries().isEmpty())
return null;
@ -131,8 +131,8 @@ class CriteriaQueryProcessor {
return query;
}
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry,/* OperationKey key, Object value,*/ String fieldName) {
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry,
/* OperationKey key, Object value,*/ String fieldName) {
Object value = entry.getValue();
if (value == null) {
return null;
@ -180,17 +180,15 @@ class CriteriaQueryProcessor {
query = fuzzyQuery(fieldName, searchText);
break;
case IN:
query = boolQuery();
collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).should(queryStringQuery(item.toString()).field(fieldName));
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
query = queryStringQuery(orQueryString(iterable)).field(fieldName);
}
break;
case NOT_IN:
query = boolQuery();
collection = (Iterable<Object>) value;
for (Object item : collection) {
((BoolQueryBuilder) query).mustNot(queryStringQuery(item.toString()).field(fieldName));
if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
query = queryStringQuery("NOT(" + orQueryString(iterable) + ')').field(fieldName);
}
break;
}
@ -203,4 +201,23 @@ class CriteriaQueryProcessor {
}
query.boost(boost);
}
private static String orQueryString(Iterable<?> iterable) {
StringBuilder sb = new StringBuilder();
for (Object item : iterable) {
if (item != null) {
if (sb.length() > 0) {
sb.append(' ');
}
sb.append('"');
sb.append(QueryParserUtil.escape(item.toString()));
sb.append('"');
}
}
return sb.toString();
}
}

View File

@ -15,10 +15,13 @@
*/
package org.springframework.data.elasticsearch.core.query;
import java.util.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.core.geo.GeoBox;
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
@ -26,6 +29,8 @@ import org.springframework.data.geo.Box;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Point;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;
import org.springframework.util.StringUtils;
/**
* Criteria is the central class when constructing queries. It follows more or less a fluent API style, which allows to
@ -39,13 +44,9 @@ public class Criteria {
@Override
public String toString() {
return "Criteria{" +
"field=" + field.getName() +
", boost=" + boost +
", negating=" + negating +
", queryCriteria=" + ObjectUtils.nullSafeToString(queryCriteria) +
", filterCriteria=" + ObjectUtils.nullSafeToString(filterCriteria) +
'}';
return "Criteria{" + "field=" + field.getName() + ", boost=" + boost + ", negating=" + negating + ", queryCriteria="
+ ObjectUtils.nullSafeToString(queryCriteria) + ", filterCriteria="
+ ObjectUtils.nullSafeToString(filterCriteria) + '}';
}
public static final String WILDCARD = "*";
@ -64,8 +65,7 @@ public class Criteria {
private Set<CriteriaEntry> filterCriteria = new LinkedHashSet<>();
public Criteria() {
}
public Criteria() {}
/**
* Creates a new Criteria with provided field name
@ -376,11 +376,6 @@ public class Criteria {
}
private List<Object> toCollection(Object... values) {
if (values.length == 0 || (values.length > 1 && values[1] instanceof Collection)) {
throw new InvalidDataAccessApiUsageException("At least one element "
+ (values.length > 0 ? ("of argument of type " + values[1].getClass().getName()) : "")
+ " has to be present.");
}
return Arrays.asList(values);
}
@ -398,9 +393,8 @@ public class Criteria {
* Creates new CriteriaEntry for {@code location WITHIN distance}
*
* @param location {@link org.springframework.data.elasticsearch.core.geo.GeoPoint} center coordinates
* @param distance {@link String} radius as a string (e.g. : '100km').
* Distance unit :
* either mi/miles or km can be set
* @param distance {@link String} radius as a string (e.g. : '100km'). Distance unit : either mi/miles or km can be
* set
* @return Criteria the chaind criteria with the new 'within' criteria included.
*/
public Criteria within(GeoPoint location, String distance) {
@ -414,8 +408,7 @@ public class Criteria {
* Creates new CriteriaEntry for {@code location WITHIN distance}
*
* @param location {@link org.springframework.data.geo.Point} center coordinates
* @param distance {@link org.springframework.data.geo.Distance} radius
* .
* @param distance {@link org.springframework.data.geo.Distance} radius .
* @return Criteria the chaind criteria with the new 'within' criteria included.
*/
public Criteria within(Point location, Distance distance) {
@ -428,13 +421,9 @@ public class Criteria {
/**
* Creates new CriteriaEntry for {@code geoLocation WITHIN distance}
*
* @param geoLocation {@link String} center point
* supported formats:
* lat on = > "41.2,45.1",
* geohash = > "asd9as0d"
* @param distance {@link String} radius as a string (e.g. : '100km').
* Distance unit :
* either mi/miles or km can be set
* @param geoLocation {@link String} center point supported formats: lat on = > "41.2,45.1", geohash = > "asd9as0d"
* @param distance {@link String} radius as a string (e.g. : '100km'). Distance unit : either mi/miles or km can be
* set
* @return
*/
public Criteria within(String geoLocation, String distance) {
@ -446,7 +435,8 @@ public class Criteria {
/**
* Creates new CriteriaEntry for {@code location GeoBox bounding box}
*
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner + right bottom corner)
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner +
* right bottom corner)
* @return Criteria the chaind criteria with the new 'boundingBox' criteria included.
*/
public Criteria boundedBy(GeoBox boundingBox) {
@ -458,12 +448,14 @@ public class Criteria {
/**
* Creates new CriteriaEntry for {@code location Box bounding box}
*
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner + right bottom corner)
* @param boundingBox {@link org.springframework.data.elasticsearch.core.geo.GeoBox} bounding box(left top corner +
* right bottom corner)
* @return Criteria the chaind criteria with the new 'boundingBox' criteria included.
*/
public Criteria boundedBy(Box boundingBox) {
Assert.notNull(boundingBox, "boundingBox value for boundedBy criteria must not be null");
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{boundingBox.getFirst(), boundingBox.getSecond()}));
filterCriteria
.add(new CriteriaEntry(OperationKey.BBOX, new Object[] { boundingBox.getFirst(), boundingBox.getSecond() }));
return this;
}
@ -498,7 +490,8 @@ public class Criteria {
public Criteria boundedBy(Point topLeftPoint, Point bottomRightPoint) {
Assert.notNull(topLeftPoint, "topLeftPoint must not be null");
Assert.notNull(bottomRightPoint, "bottomRightPoint must not be null");
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX, new Object[]{GeoPoint.fromPoint(topLeftPoint), GeoPoint.fromPoint(bottomRightPoint)}));
filterCriteria.add(new CriteriaEntry(OperationKey.BBOX,
new Object[] { GeoPoint.fromPoint(topLeftPoint), GeoPoint.fromPoint(bottomRightPoint) }));
return this;
}
@ -611,10 +604,7 @@ public class Criteria {
@Override
public String toString() {
return "CriteriaEntry{" +
"key=" + key +
", value=" + value +
'}';
return "CriteriaEntry{" + "key=" + key + ", value=" + value + '}';
}
}
}

View File

@ -155,7 +155,13 @@ public abstract class AbstractElasticsearchRepository<T, ID> implements Elastics
Assert.notNull(ids, "ids can't be null.");
SearchQuery query = new NativeSearchQueryBuilder().withIds(stringIdsRepresentation(ids)).build();
List<String> stringIds = stringIdsRepresentation(ids);
if (stringIds.isEmpty()) {
return Collections.emptyList();
}
SearchQuery query = new NativeSearchQueryBuilder().withIds(stringIds).build();
return elasticsearchOperations.multiGet(query, getEntityClass());
}

View File

@ -23,6 +23,7 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
@ -197,6 +198,22 @@ abstract class QueryKeywordsTests {
assertThat(sortedIds).containsExactly("5", "4", "3", "2", "1");
}
@Test // DATAES-937
public void shouldReturnEmptyListOnFindByIdWithEmptyInputList() {
Iterable<Product> products = repository.findAllById(new ArrayList<>());
assertThat(products).isEmpty();
}
@Test // DATAES-937
public void shouldReturnEmptyListOnDerivedMethodWithEmptyInputList() {
Iterable<Product> products = repository.findAllByNameIn(new ArrayList<>());
assertThat(products).isEmpty();
}
/**
* @author Mohsin Husen
* @author Artur Konczak
@ -279,6 +296,8 @@ abstract class QueryKeywordsTests {
List<Product> findAllByOrderByText();
List<Product> findAllByOrderBySortName();
List<Product> findAllByNameIn(List<String> names);
}
}