DATAES-706 - CriteriaQueryProcessor must handle nested Criteria definitions.

Original PR: #505
This commit is contained in:
Peter-Josef Meisch 2020-08-18 20:59:35 +02:00 committed by GitHub
parent c8c6e7a646
commit 131f0318cc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 1426 additions and 631 deletions

View File

@ -1,7 +1,8 @@
[[preface]] [[preface]]
= Preface = Preface
The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine. It provides: The Spring Data Elasticsearch project applies core Spring concepts to the development of solutions using the Elasticsearch Search Engine.
It provides:
* _Templates_ as a high-level abstraction for storing, searching, sorting documents and building aggregations. * _Templates_ as a high-level abstraction for storing, searching, sorting documents and building aggregations.
* _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>). * _Repositories_ which for example enable the user to express queries by defining interfaces having customized method names (for basic information about repositories see <<repositories>>).
@ -29,12 +30,13 @@ Requires an installation of https://www.elastic.co/products/elasticsearch[Elasti
=== Versions === Versions
The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of Spring Data Elasticsearch included in that, as well as the Spring Boot versions referring to that particular Spring Data release train: The following table shows the Elasticsearch versions that are used by Spring Data release trains and version of Spring Data Elasticsearch included in that, as well as the Spring Boot versions referring to that particular Spring Data release train:
[cols="^,^,^,^",options="header"] [cols="^,^,^,^",options="header"]
|=== |===
| Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot | Spring Data Release Train |Spring Data Elasticsearch |Elasticsearch | Spring Boot
| 2020.0.0footnote:cdv[Currently in development] |4.1.xfootnote:cdv[]|7.8.1 |2.3.xfootnote:cdv[] | 2020.0.0footnote:cdv[Currently in development] |4.1.xfootnote:cdv[]|7.8.1 |2.3.xfootnote:cdv[]
| Neumann | 4.0.x | 7.6.2 |2.3.x | Neumann | 4.0.x | 7.6.2 |2.3.x
| Moore | 3.2.x |6.8.12 | 2.2.x | Moore | 3.2.x |6.8.10 | 2.2.x
| Lovelace | 3.1.x | 6.2.2 |2.1.x | Lovelace | 3.1.x | 6.2.2 |2.1.x
| Kayfootnote:oom[Out of maintenance] | 3.0.xfootnote:oom[] | 5.5.0 | 2.0.xfootnote:oom[] | Kayfootnote:oom[Out of maintenance] | 3.0.xfootnote:oom[] | 5.5.0 | 2.0.xfootnote:oom[]
| Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 1.5.xfootnote:oom[] | Ingallsfootnote:oom[] | 2.1.xfootnote:oom[] | 2.4.0 | 1.5.xfootnote:oom[]

View File

@ -45,7 +45,8 @@ public class TransportClientConfig extends ElasticsearchConfigurationSupport {
} }
} }
---- ----
<1> Setting up the <<elasticsearch.clients.transport>>. Deprecated as of version 4.0. <1> Setting up the <<elasticsearch.clients.transport>>.
Deprecated as of version 4.0.
<2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_. <2> Creating the `ElasticsearchTemplate` bean, offering both names, _elasticsearchOperations_ and _elasticsearchTemplate_.
==== ====
@ -75,7 +76,9 @@ public class RestClientConfig extends AbstractElasticsearchConfiguration {
[[elasticsearch.operations.usage]] [[elasticsearch.operations.usage]]
== Usage examples == Usage examples
As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different. The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller. The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above. As both `ElasticsearchTemplate` and `ElasticsearchRestTemplate` implement the `ElasticsearchOperations` interface, the code to use them is not different.
The example shows how to use an injected `ElasticsearchOperations` instance in a Spring REST controller.
The decision, if this is using the `TransportClient` or the `RestClient` is made by providing the corresponding Bean with one of the configurations shown above.
.ElasticsearchOperations usage .ElasticsearchOperations usage
==== ====
@ -123,9 +126,12 @@ include::reactive-elasticsearch-operations.adoc[leveloffset=+1]
[[elasticsearch.operations.searchresulttypes]] [[elasticsearch.operations.searchresulttypes]]
== Search Result Types == Search Result Types
When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned. When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity. When a document is retrieved with the methods of the `DocumentOperations` interface, just the found entity will be returned.
When searching with the methods of the `SearchOperations` interface, additional information is available for each entity, for example the _score_ or the _sortValues_ of the found entity.
In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information. These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations. The following classes and interfaces are now available: In order to return this information, each entity is wrapped in a `SearchHit` object that contains this entity-specific additional information.
These `SearchHit` objects themselves are returned within a `SearchHits` object which additionally contains informations about the whole search like the _maxScore_ or requested aggregations.
The following classes and interfaces are now available:
.SearchHit<T> .SearchHit<T>
Contains the following information: Contains the following information:
@ -155,3 +161,108 @@ Returned by the low level scroll API functions in `ElasticsearchRestTemplate`, i
.SearchHitsIterator<T> .SearchHitsIterator<T>
An Iterator returned by the streaming functions of the `SearchOperations` interface. An Iterator returned by the streaming functions of the `SearchOperations` interface.
== Queries
Almost all of the methods defined in the `SearchOperations` and `ReactiveSearchOperations` interface take a `Query` parameter that defines the query to execute for searching. `Query` is an interface and Spring Data Elasticsearch provides three implementations: `CriteriaQuery`, `StringQuery` and `NativeSearchQuery`.
=== CriteriaQuery
`CriteriaQuery` based queries allow the creation of queries to search for data without knowing the syntax or basics of Elasticsearch queries. They allow the user to build queries by simply chaining and combining `Criteria` objects that specifiy the criteria the searched documents must fulfill.
NOTE: when talking about AND or OR when combining criteria keep in mind, that in Elasticsearch AND are converted to a **must** condition and OR to a **should**
`Criteria` and their usage are best explained by example
(let's assume we have a `Book` entity with a `price` property):
.Get books with a given price
====
[source,java]
----
Criteria criteria = new Criteria("price").is(42.0);
Query query = new CriteriaQuery(criteria);
----
====
Conditions for the same field can be chained, they will be combined with a logical AND:
.Get books with a given price
====
[source,java]
----
Criteria criteria = new Criteria("price").greaterThan(42.0).lessThan(34.0L);
Query query = new CriteriaQuery(criteria);
----
====
When chaining `Criteria`, by default a AND logic is used:
.Get all persons with first name _James_ and last name _Miller_:
====
[source,java]
----
Criteria criteria = new Criteria("lastname").is("Miller") <1>
.and("firstname").is("James") <2>
Query query = new CriteriaQuery(criteria);
----
<1> the first `Criteria`
<2> the and() creates a new `Criteria` and chaines it to the first one.
====
If you want to create nested queries, you need to use subqueries for this. Let's assume we want to find all persons with a last name of _Miller_ and a first name of either _Jack_ or _John_:
.Nested subqueries
====
[source,java]
----
Criteria miller = new Criteria("lastName").is("Miller") <.>
.subCriteria( <.>
new Criteria().or("firstName").is("John") <.>
.or("firstName").is("Jack") <.>
);
Query query = new CriteriaQuery(criteria);
----
<.> create a first `Criteria` for the last name
<.> this is combined with AND to a subCriteria
<.> This sub Criteria is an OR combination for the first name _John_
<.> and the first name Jack
====
Please refer to the API documentation of the `Criteria` class for a complete overview of the different available operations.
=== StringQuery
This class takes an Elasticsearch query as JSON String.
The following code shows a query that searches for persons having the first name "Jack":
====
[source,java]
----
Query query = new SearchQuery("{ \"match\": { \"firstname\": { \"query\": \"Jack\" } } } ");
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====
Using `StringQuery` may be appropriate if you already have an Elasticsearch query to use.
=== NativeSearchQuery
`NativeSearchQuery` is the class to use when you have a complex query, or a query that cannot be expressed by using the `Criteria` API, for example when building queries and using aggregates.
It allows to use all the different `QueryBuilder` implementations from the Elasticsearch library therefore named "native".
The following code shows how to search for persons with a given firstname and for the found documents have a terms aggregation that counts the number of occurences of the lastnames for these persons:
====
[source,java]
----
Query query = new NativeSearchQueryBuilder()
.addAggregation(terms("lastnames").field("lastname").size(10)) //
.withQuery(QueryBuilders.matchQuery("firstname", firstName))
.build();
SearchHits<Person> searchHits = operations.search(query, Person.class);
----
====

View File

@ -17,9 +17,11 @@ package org.springframework.data.elasticsearch.core;
import static org.springframework.data.elasticsearch.core.query.Criteria.*; import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.stream.Collectors;
import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.BoolQueryBuilder;
@ -47,66 +49,56 @@ import org.springframework.util.Assert;
*/ */
class CriteriaFilterProcessor { class CriteriaFilterProcessor {
QueryBuilder createFilterFromCriteria(Criteria criteria) { @Nullable
List<QueryBuilder> fbList = new LinkedList<>(); QueryBuilder createFilter(Criteria criteria) {
QueryBuilder filter = null;
List<QueryBuilder> filterBuilders = new ArrayList<>();
for (Criteria chainedCriteria : criteria.getCriteriaChain()) { for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
QueryBuilder fb = null;
if (chainedCriteria.isOr()) { if (chainedCriteria.isOr()) {
fb = QueryBuilders.boolQuery(); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
for (QueryBuilder f : createFilterFragmentForCriteria(chainedCriteria)) { queriesForEntries(chainedCriteria).forEach(boolQuery::should);
((BoolQueryBuilder) fb).should(f); filterBuilders.add(boolQuery);
}
fbList.add(fb);
} else if (chainedCriteria.isNegating()) { } else if (chainedCriteria.isNegating()) {
List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(), List<QueryBuilder> negationFilters = buildNegationFilter(criteria.getField().getName(),
criteria.getFilterCriteriaEntries().iterator()); criteria.getFilterCriteriaEntries().iterator());
if (!negationFilters.isEmpty()) { filterBuilders.addAll(negationFilters);
fbList.addAll(negationFilters);
}
} else { } else {
fbList.addAll(createFilterFragmentForCriteria(chainedCriteria)); filterBuilders.addAll(queriesForEntries(chainedCriteria));
} }
} }
if (!fbList.isEmpty()) { QueryBuilder filter = null;
if (fbList.size() == 1) {
filter = fbList.get(0); if (!filterBuilders.isEmpty()) {
if (filterBuilders.size() == 1) {
filter = filterBuilders.get(0);
} else { } else {
filter = QueryBuilders.boolQuery(); BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
for (QueryBuilder f : fbList) { filterBuilders.forEach(boolQuery::must);
((BoolQueryBuilder) filter).must(f); filter = boolQuery;
}
} }
} }
return filter; return filter;
} }
private List<QueryBuilder> createFilterFragmentForCriteria(Criteria chainedCriteria) { private List<QueryBuilder> queriesForEntries(Criteria criteria) {
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getFilterCriteriaEntries().iterator();
List<QueryBuilder> filterList = new LinkedList<>();
String fieldName = chainedCriteria.getField().getName(); Assert.notNull(criteria.getField(), "criteria must have a field");
String fieldName = criteria.getField().getName();
Assert.notNull(fieldName, "Unknown field"); Assert.notNull(fieldName, "Unknown field");
QueryBuilder filter = null;
while (it.hasNext()) { return criteria.getFilterCriteriaEntries().stream()
Criteria.CriteriaEntry entry = it.next(); .map(entry -> queryFor(entry.getKey(), entry.getValue(), fieldName)).collect(Collectors.toList());
filter = processCriteriaEntry(entry.getKey(), entry.getValue(), fieldName);
filterList.add(filter);
}
return filterList;
} }
@Nullable @Nullable
private QueryBuilder processCriteriaEntry(OperationKey key, Object value, String fieldName) { private QueryBuilder queryFor(OperationKey key, Object value, String fieldName) {
if (value == null) {
return null;
}
QueryBuilder filter = null; QueryBuilder filter = null;
switch (key) { switch (key) {
@ -169,8 +161,7 @@ class CriteriaFilterProcessor {
// 2x text // 2x text
twoParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray); twoParameterBBox((GeoBoundingBoxQueryBuilder) filter, valArray);
} else { } else {
// error throw new IllegalArgumentException(
Assert.isTrue(false,
"Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash))."); "Geo distance filter takes a 1-elements array(GeoBox) or 2-elements array(GeoPoints or Strings(format lat,lon or geohash)).");
} }
break; break;
@ -208,8 +199,7 @@ class CriteriaFilterProcessor {
GeoBox geoBBox; GeoBox geoBBox;
if (value instanceof Box) { if (value instanceof Box) {
Box sdbox = (Box) value; geoBBox = GeoBox.fromBox((Box) value);
geoBBox = GeoBox.fromBox(sdbox);
} else { } else {
geoBBox = (GeoBox) value; geoBBox = (GeoBox) value;
} }
@ -218,7 +208,7 @@ class CriteriaFilterProcessor {
geoBBox.getBottomRight().getLon()); geoBBox.getBottomRight().getLon());
} }
private static boolean isType(Object[] array, Class clazz) { private static boolean isType(Object[] array, Class<?> clazz) {
for (Object o : array) { for (Object o : array) {
if (!clazz.isInstance(o)) { if (!clazz.isInstance(o)) {
return false; return false;
@ -247,7 +237,7 @@ class CriteriaFilterProcessor {
while (it.hasNext()) { while (it.hasNext()) {
Criteria.CriteriaEntry criteriaEntry = it.next(); Criteria.CriteriaEntry criteriaEntry = it.next();
QueryBuilder notFilter = QueryBuilders.boolQuery() QueryBuilder notFilter = QueryBuilders.boolQuery()
.mustNot(processCriteriaEntry(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName)); .mustNot(queryFor(criteriaEntry.getKey(), criteriaEntry.getValue(), fieldName));
notFilterList.add(notFilter); notFilterList.add(notFilter);
} }

View File

@ -21,11 +21,8 @@ import static org.springframework.data.elasticsearch.core.query.Criteria.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.ListIterator;
import org.apache.lucene.queryparser.flexible.core.util.StringUtils;
import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil; import org.apache.lucene.queryparser.flexible.standard.QueryParserUtil;
import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilder;
@ -46,63 +43,81 @@ import org.springframework.util.Assert;
*/ */
class CriteriaQueryProcessor { class CriteriaQueryProcessor {
QueryBuilder createQueryFromCriteria(Criteria criteria) { @Nullable
QueryBuilder createQuery(Criteria criteria) {
Assert.notNull(criteria, "criteria must not be null"); Assert.notNull(criteria, "criteria must not be null");
List<QueryBuilder> shouldQueryBuilderList = new LinkedList<>(); List<QueryBuilder> shouldQueryBuilders = new ArrayList<>();
List<QueryBuilder> mustNotQueryBuilderList = new LinkedList<>(); List<QueryBuilder> mustNotQueryBuilders = new ArrayList<>();
List<QueryBuilder> mustQueryBuilderList = new LinkedList<>(); List<QueryBuilder> mustQueryBuilders = new ArrayList<>();
ListIterator<Criteria> chainIterator = criteria.getCriteriaChain().listIterator();
QueryBuilder firstQuery = null; QueryBuilder firstQuery = null;
boolean negateFirstQuery = false; boolean negateFirstQuery = false;
while (chainIterator.hasNext()) { for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
Criteria chainedCriteria = chainIterator.next(); QueryBuilder queryFragment = queryForEntries(chainedCriteria);
QueryBuilder queryFragmentForCriteria = createQueryFragmentForCriteria(chainedCriteria);
if (queryFragmentForCriteria != null) { if (queryFragment != null) {
if (firstQuery == null) { if (firstQuery == null) {
firstQuery = queryFragmentForCriteria; firstQuery = queryFragment;
negateFirstQuery = chainedCriteria.isNegating(); negateFirstQuery = chainedCriteria.isNegating();
continue; continue;
} }
if (chainedCriteria.isOr()) { if (chainedCriteria.isOr()) {
shouldQueryBuilderList.add(queryFragmentForCriteria); shouldQueryBuilders.add(queryFragment);
} else if (chainedCriteria.isNegating()) { } else if (chainedCriteria.isNegating()) {
mustNotQueryBuilderList.add(queryFragmentForCriteria); mustNotQueryBuilders.add(queryFragment);
} else { } else {
mustQueryBuilderList.add(queryFragmentForCriteria); mustQueryBuilders.add(queryFragment);
}
}
}
for (Criteria subCriteria : criteria.getSubCriteria()) {
QueryBuilder subQuery = createQuery(subCriteria);
if (subQuery != null) {
if (criteria.isOr()) {
shouldQueryBuilders.add(subQuery);
} else if (criteria.isNegating()) {
mustNotQueryBuilders.add(subQuery);
} else {
mustQueryBuilders.add(subQuery);
} }
} }
} }
if (firstQuery != null) { if (firstQuery != null) {
if (!shouldQueryBuilderList.isEmpty() && mustNotQueryBuilderList.isEmpty() && mustQueryBuilderList.isEmpty()) {
shouldQueryBuilderList.add(0, firstQuery); if (!shouldQueryBuilders.isEmpty() && mustNotQueryBuilders.isEmpty() && mustQueryBuilders.isEmpty()) {
shouldQueryBuilders.add(0, firstQuery);
} else { } else {
if (negateFirstQuery) { if (negateFirstQuery) {
mustNotQueryBuilderList.add(0, firstQuery); mustNotQueryBuilders.add(0, firstQuery);
} else { } else {
mustQueryBuilderList.add(0, firstQuery); mustQueryBuilders.add(0, firstQuery);
} }
} }
} }
BoolQueryBuilder query = null; BoolQueryBuilder query = null;
if (!shouldQueryBuilderList.isEmpty() || !mustNotQueryBuilderList.isEmpty() || !mustQueryBuilderList.isEmpty()) { if (!shouldQueryBuilders.isEmpty() || !mustNotQueryBuilders.isEmpty() || !mustQueryBuilders.isEmpty()) {
query = boolQuery(); query = boolQuery();
for (QueryBuilder qb : shouldQueryBuilderList) { for (QueryBuilder qb : shouldQueryBuilders) {
query.should(qb); query.should(qb);
} }
for (QueryBuilder qb : mustNotQueryBuilderList) { for (QueryBuilder qb : mustNotQueryBuilders) {
query.mustNot(qb); query.mustNot(qb);
} }
for (QueryBuilder qb : mustQueryBuilderList) { for (QueryBuilder qb : mustQueryBuilders) {
query.must(qb); query.must(qb);
} }
} }
@ -111,46 +126,41 @@ class CriteriaQueryProcessor {
} }
@Nullable @Nullable
private QueryBuilder createQueryFragmentForCriteria(Criteria chainedCriteria) { private QueryBuilder queryForEntries(Criteria criteria) {
if (chainedCriteria.getQueryCriteriaEntries().isEmpty())
if (criteria.getField() == null || criteria.getQueryCriteriaEntries().isEmpty())
return null; return null;
Iterator<Criteria.CriteriaEntry> it = chainedCriteria.getQueryCriteriaEntries().iterator(); String fieldName = criteria.getField().getName();
boolean singeEntryCriteria = (chainedCriteria.getQueryCriteriaEntries().size() == 1);
String fieldName = chainedCriteria.getField().getName();
Assert.notNull(fieldName, "Unknown field"); Assert.notNull(fieldName, "Unknown field");
QueryBuilder query = null;
if (singeEntryCriteria) { Iterator<Criteria.CriteriaEntry> it = criteria.getQueryCriteriaEntries().iterator();
Criteria.CriteriaEntry entry = it.next(); QueryBuilder query;
query = processCriteriaEntry(entry, fieldName);
if (criteria.getQueryCriteriaEntries().size() == 1) {
query = queryFor(it.next(), fieldName);
} else { } else {
query = boolQuery(); query = boolQuery();
while (it.hasNext()) { while (it.hasNext()) {
Criteria.CriteriaEntry entry = it.next(); Criteria.CriteriaEntry entry = it.next();
((BoolQueryBuilder) query).must(processCriteriaEntry(entry, fieldName)); ((BoolQueryBuilder) query).must(queryFor(entry, fieldName));
} }
} }
addBoost(query, chainedCriteria.getBoost()); addBoost(query, criteria.getBoost());
return query; return query;
} }
@Nullable @Nullable
private QueryBuilder processCriteriaEntry(Criteria.CriteriaEntry entry, String fieldName) { private QueryBuilder queryFor(Criteria.CriteriaEntry entry, String fieldName) {
OperationKey key = entry.getKey(); OperationKey key = entry.getKey();
Object value = entry.getValue();
if (value == null) { if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
if (key == OperationKey.EXISTS) {
return existsQuery(fieldName);
} else {
return null;
}
} }
Object value = entry.getValue();
String searchText = QueryParserUtil.escape(value.toString()); String searchText = QueryParserUtil.escape(value.toString());
QueryBuilder query = null; QueryBuilder query = null;
@ -190,11 +200,23 @@ class CriteriaQueryProcessor {
case FUZZY: case FUZZY:
query = fuzzyQuery(fieldName, searchText); query = fuzzyQuery(fieldName, searchText);
break; break;
case MATCHES:
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.OR);
break;
case MATCHES_ALL:
query = matchQuery(fieldName, value).operator(org.elasticsearch.index.query.Operator.AND);
break;
case IN: case IN:
query = boolQuery().must(termsQuery(fieldName, toStringList((Iterable<Object>) value))); if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
query = boolQuery().must(termsQuery(fieldName, toStringList(iterable)));
}
break; break;
case NOT_IN: case NOT_IN:
query = boolQuery().mustNot(termsQuery(fieldName, toStringList((Iterable<Object>) value))); if (value instanceof Iterable) {
Iterable<?> iterable = (Iterable<?>) value;
query = boolQuery().mustNot(termsQuery(fieldName, toStringList(iterable)));
}
break; break;
} }
return query; return query;
@ -203,15 +225,17 @@ class CriteriaQueryProcessor {
private static List<String> toStringList(Iterable<?> iterable) { private static List<String> toStringList(Iterable<?> iterable) {
List<String> list = new ArrayList<>(); List<String> list = new ArrayList<>();
for (Object item : iterable) { for (Object item : iterable) {
list.add(StringUtils.toString(item)); list.add(item != null ? item.toString() : null);
} }
return list; return list;
} }
private void addBoost(QueryBuilder query, float boost) { private void addBoost(@Nullable QueryBuilder query, float boost) {
if (Float.isNaN(boost)) {
if (query == null || Float.isNaN(boost)) {
return; return;
} }
query.boost(boost); query.boost(boost);
} }
} }

View File

@ -1513,7 +1513,7 @@ class RequestFactory {
elasticsearchQuery = searchQuery.getQuery(); elasticsearchQuery = searchQuery.getQuery();
} else if (query instanceof CriteriaQuery) { } else if (query instanceof CriteriaQuery) {
CriteriaQuery criteriaQuery = (CriteriaQuery) query; CriteriaQuery criteriaQuery = (CriteriaQuery) query;
elasticsearchQuery = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria()); elasticsearchQuery = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria());
} else if (query instanceof StringQuery) { } else if (query instanceof StringQuery) {
StringQuery stringQuery = (StringQuery) query; StringQuery stringQuery = (StringQuery) query;
elasticsearchQuery = wrapperQuery(stringQuery.getSource()); elasticsearchQuery = wrapperQuery(stringQuery.getSource());
@ -1533,7 +1533,7 @@ class RequestFactory {
elasticsearchFilter = searchQuery.getFilter(); elasticsearchFilter = searchQuery.getFilter();
} else if (query instanceof CriteriaQuery) { } else if (query instanceof CriteriaQuery) {
CriteriaQuery criteriaQuery = (CriteriaQuery) query; CriteriaQuery criteriaQuery = (CriteriaQuery) query;
elasticsearchFilter = new CriteriaFilterProcessor().createFilterFromCriteria(criteriaQuery.getCriteria()); elasticsearchFilter = new CriteriaFilterProcessor().createFilter(criteriaQuery.getCriteria());
} else if (query instanceof StringQuery) { } else if (query instanceof StringQuery) {
elasticsearchFilter = null; elasticsearchFilter = null;
} else { } else {

View File

@ -48,6 +48,7 @@ import org.springframework.data.elasticsearch.core.join.JoinField;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter; import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentPropertyConverter;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery; import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm; import org.springframework.data.elasticsearch.core.query.SeqNoPrimaryTerm;
import org.springframework.data.mapping.PersistentPropertyAccessor; import org.springframework.data.mapping.PersistentPropertyAccessor;
@ -761,29 +762,39 @@ public class MappingElasticsearchConverter
ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass); ElasticsearchPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(domainClass);
if (persistentEntity != null) { if (persistentEntity != null) {
criteriaQuery.getCriteria().getCriteriaChain().forEach(criteria -> { for (Criteria chainedCriteria : criteriaQuery.getCriteria().getCriteriaChain()) {
String name = criteria.getField().getName(); updateCriteria(chainedCriteria, persistentEntity);
ElasticsearchPersistentProperty property = persistentEntity.getPersistentProperty(name); }
}
}
if (property != null && property.getName().equals(name)) { private void updateCriteria(Criteria criteria, ElasticsearchPersistentEntity<?> persistentEntity) {
criteria.getField().setName(property.getFieldName()); String name = criteria.getField().getName();
ElasticsearchPersistentProperty property = persistentEntity.getPersistentProperty(name);
if (property.hasPropertyConverter()) { if (property != null && property.getName().equals(name)) {
ElasticsearchPersistentPropertyConverter propertyConverter = property.getPropertyConverter(); criteria.getField().setName(property.getFieldName());
criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
Object value = criteriaEntry.getValue(); if (property.hasPropertyConverter()) {
if (value.getClass().isArray()) { ElasticsearchPersistentPropertyConverter propertyConverter = property.getPropertyConverter();
Object[] objects = (Object[]) value; criteria.getQueryCriteriaEntries().forEach(criteriaEntry -> {
for (int i = 0; i < objects.length; i++) { Object value = criteriaEntry.getValue();
objects[i] = propertyConverter.write(objects[i]); if (value.getClass().isArray()) {
} Object[] objects = (Object[]) value;
} else { for (int i = 0; i < objects.length; i++) {
criteriaEntry.setValue(propertyConverter.write(value)); objects[i] = propertyConverter.write(objects[i]);
} }
}); } else {
criteriaEntry.setValue(propertyConverter.write(value));
} }
} });
}); }
}
for (Criteria subCriteria : criteria.getSubCriteria()) {
for (Criteria chainedCriteria : subCriteria.getCriteriaChain()) {
updateCriteria(chainedCriteria, persistentEntity);
}
} }
} }
// endregion // endregion

View File

@ -18,7 +18,8 @@ package org.springframework.data.elasticsearch.core.query;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
* The most trivial implementation of a Field * The most trivial implementation of a Field. The {@link #name} is updateable, so it may be changed during query
* preparation by the {@link org.springframework.data.elasticsearch.core.convert.MappingElasticsearchConverter}.
* *
* @author Rizwan Idrees * @author Rizwan Idrees
* @author Mohsin Husen * @author Mohsin Husen
@ -29,25 +30,27 @@ public class SimpleField implements Field {
private String name; private String name;
public SimpleField(String name) { public SimpleField(String name) {
Assert.notNull(name, "name must not be null");
Assert.hasText(name, "name must not be null");
this.name = name; this.name = name;
} }
@Override @Override
public void setName(String name) { public void setName(String name) {
Assert.notNull(name, "name must not be null");
Assert.hasText(name, "name must not be null");
this.name = name; this.name = name;
} }
@Override @Override
public String getName() { public String getName() {
return this.name; return name;
} }
@Override @Override
public String toString() { public String toString() {
return this.name; return getName();
} }
} }

View File

@ -96,13 +96,10 @@ public class ElasticsearchQueryCreator extends AbstractQueryCreator<CriteriaQuer
return query.addSort(sort); return query.addSort(sort);
} }
private Criteria from(Part part, Criteria instance, Iterator<?> parameters) { private Criteria from(Part part, Criteria criteria, Iterator<?> parameters) {
Part.Type type = part.getType(); Part.Type type = part.getType();
Criteria criteria = instance;
if (criteria == null) {
criteria = new Criteria();
}
switch (type) { switch (type) {
case TRUE: case TRUE:
return criteria.is(true); return criteria.is(true);

View File

@ -56,12 +56,15 @@ public class CriteriaQueryMappingTests {
} }
@Test @Test // DATAES-716
void shouldMapNamesAndConvertValuesInCriteriaQuery() throws JSONException { void shouldMapNamesAndConvertValuesInCriteriaQuery() throws JSONException {
// use POJO properties and types in the query building // use POJO properties and types in the query building
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria("birthDate") CriteriaQuery criteriaQuery = new CriteriaQuery( //
.between(LocalDate.of(1989, 11, 9), LocalDate.of(1990, 11, 9)).or("birthDate").is(LocalDate.of(2019, 12, 28))); new Criteria("birthDate") //
.between(LocalDate.of(1989, 11, 9), LocalDate.of(1990, 11, 9)) //
.or("birthDate").is(LocalDate.of(2019, 12, 28)) //
);
// mapped field name and converted parameter // mapped field name and converted parameter
String expected = '{' + // String expected = '{' + //
@ -90,7 +93,60 @@ public class CriteriaQueryMappingTests {
'}'; // '}'; //
mappingElasticsearchConverter.updateQuery(criteriaQuery, Person.class); mappingElasticsearchConverter.updateQuery(criteriaQuery, Person.class);
String queryString = new CriteriaQueryProcessor().createQueryFromCriteria(criteriaQuery.getCriteria()).toString(); String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString();
assertEquals(expected, queryString, false);
}
@Test // DATAES-706
void shouldMapNamesAndValuesInSubCriteriaQuery() throws JSONException {
CriteriaQuery criteriaQuery = new CriteriaQuery( //
new Criteria("firstName").matches("John") //
.subCriteria(new Criteria("birthDate") //
.between(LocalDate.of(1989, 11, 9), LocalDate.of(1990, 11, 9)) //
.or("birthDate").is(LocalDate.of(2019, 12, 28))));
String expected = "{\n" + //
" \"bool\": {\n" + //
" \"must\": [\n" + //
" {\n" + //
" \"match\": {\n" + //
" \"first-name\": {\n" + //
" \"query\": \"John\"\n" + //
" }\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"bool\": {\n" + //
" \"should\": [\n" + //
" {\n" + //
" \"range\": {\n" + //
" \"birth-date\": {\n" + //
" \"from\": \"09.11.1989\",\n" + //
" \"to\": \"09.11.1990\",\n" + //
" \"include_lower\": true,\n" + //
" \"include_upper\": true\n" + //
" }\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"28.12.2019\",\n" + //
" \"fields\": [\n" + //
" \"birth-date^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}\n"; //
mappingElasticsearchConverter.updateQuery(criteriaQuery, Person.class);
String queryString = new CriteriaQueryProcessor().createQuery(criteriaQuery.getCriteria()).toString();
assertEquals(expected, queryString, false); assertEquals(expected, queryString, false);
} }

View File

@ -0,0 +1,341 @@
/*
* Copyright 2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.data.elasticsearch.core;
import static org.skyscreamer.jsonassert.JSONAssert.*;
import org.json.JSONException;
import org.junit.jupiter.api.Test;
import org.springframework.data.elasticsearch.core.query.Criteria;
/**
* @author Peter-Josef Meisch
*/
class CriteriaQueryProcessorTests {
private final CriteriaQueryProcessor queryProcessor = new CriteriaQueryProcessor();
@Test // DATAES-706
void shouldProcessTwoCriteriaWithAnd() throws JSONException {
String expected = "{\n" + //
" \"bool\": {\n" + //
" \"must\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value1\",\n" + //
" \"fields\": [\n" + //
" \"field1^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value2\",\n" + //
" \"fields\": [\n" + //
" \"field2^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}"; //
Criteria criteria = new Criteria("field1").is("value1").and("field2").is("value2");
String query = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, query, false);
}
@Test // DATAES-706
void shouldProcessTwoCriteriaWithOr() throws JSONException {
String expected = "{\n" + //
" \"bool\": {\n" + //
" \"should\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value1\",\n" + //
" \"fields\": [\n" + //
" \"field1^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value2\",\n" + //
" \"fields\": [\n" + //
" \"field2^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}"; //
Criteria criteria = new Criteria("field1").is("value1").or("field2").is("value2");
String query = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, query, false);
}
@Test // DATAES-706
void shouldProcessMixedCriteriaWithOrAnd() throws JSONException {
String expected = "{\n" + //
" \"bool\": {\n" + //
" \"must\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value1\",\n" + //
" \"fields\": [\n" + //
" \"field1^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value3\",\n" + //
" \"fields\": [\n" + //
" \"field3^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ],\n" + //
" \"should\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value2\",\n" + //
" \"fields\": [\n" + //
" \"field2^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"value4\",\n" + //
" \"fields\": [\n" + //
" \"field4^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}\n"; //
Criteria criteria = new Criteria("field1").is("value1") //
.or("field2").is("value2") //
.and("field3").is("value3") //
.or("field4").is("value4"); //
String query = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, query, false);
}
@Test // DATAES-706
void shouldAddSubQuery() throws JSONException {
String expected = "{\n" + //
" \"bool\": {\n" + //
" \"must\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"Miller\",\n" + //
" \"fields\": [\n" + //
" \"lastName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"bool\": {\n" + //
" \"should\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"John\",\n" + //
" \"fields\": [\n" + //
" \"firstName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"Jack\",\n" + //
" \"fields\": [\n" + //
" \"firstName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}"; //
Criteria criteria = new Criteria("lastName").is("Miller")
.subCriteria(new Criteria().or("firstName").is("John").or("firstName").is("Jack"));
String query = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, query, false);
}
@Test // DATAES-706
void shouldProcessNestedSubCriteria() throws JSONException {
String expected = "{\n" + //
" \"bool\": {\n" + //
" \"should\": [\n" + //
" {\n" + //
" \"bool\": {\n" + //
" \"must\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"Miller\",\n" + //
" \"fields\": [\n" + //
" \"lastName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"bool\": {\n" + //
" \"should\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"Jack\",\n" + //
" \"fields\": [\n" + //
" \"firstName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"John\",\n" + //
" \"fields\": [\n" + //
" \"firstName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"bool\": {\n" + //
" \"must\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"Smith\",\n" + //
" \"fields\": [\n" + //
" \"lastName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"bool\": {\n" + //
" \"should\": [\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"Emma\",\n" + //
" \"fields\": [\n" + //
" \"firstName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" },\n" + //
" {\n" + //
" \"query_string\": {\n" + //
" \"query\": \"Lucy\",\n" + //
" \"fields\": [\n" + //
" \"firstName^1.0\"\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}"; //
Criteria criteria = Criteria.or()
.subCriteria(new Criteria("lastName").is("Miller")
.subCriteria(new Criteria().or("firstName").is("John").or("firstName").is("Jack")))
.subCriteria(new Criteria("lastName").is("Smith")
.subCriteria(new Criteria().or("firstName").is("Emma").or("firstName").is("Lucy")));
String query = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, query, false);
}
@Test // DATAES-706
void shouldBuildMatchQuery() throws JSONException {
String expected = "{\n" + //
" \"bool\" : {\n" + //
" \"must\" : [\n" + //
" {\n" + //
" \"match\" : {\n" + //
" \"field1\" : {\n" + //
" \"query\" : \"value1 value2\",\n" + //
" \"operator\" : \"OR\"\n" + //
" }\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}\n"; //
Criteria criteria = new Criteria("field1").matches("value1 value2");
String query = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, query, false);
}
@Test // DATAES-706
void shouldBuildMatchAllQuery() throws JSONException {
String expected = "{\n" + //
" \"bool\" : {\n" + //
" \"must\" : [\n" + //
" {\n" + //
" \"match\" : {\n" + //
" \"field1\" : {\n" + //
" \"query\" : \"value1 value2\",\n" + //
" \"operator\" : \"AND\"\n" + //
" }\n" + //
" }\n" + //
" }\n" + //
" ]\n" + //
" }\n" + //
"}\n"; //
Criteria criteria = new Criteria("field1").matchesAll("value1 value2");
String query = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, query, false);
}
}

View File

@ -83,99 +83,72 @@ public class CriteriaQueryTests {
indexOperations.delete(); indexOperations.delete();
} }
@Test @Test // ,DATAES-706
public void shouldPerformAndOperation() { public void shouldPerformAndOperationOnCriteriaEntries() {
// given // given
String documentId = nextIdAsString(); SampleEntity sampleEntity1 = new SampleEntity();
SampleEntity sampleEntity = new SampleEntity(); sampleEntity1.setId(nextIdAsString());
sampleEntity.setId(documentId); sampleEntity1.setMessage("some test message");
sampleEntity.setMessage("some test message"); operations.save(sampleEntity1);
sampleEntity.setVersion(System.currentTimeMillis()); SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setId(nextIdAsString());
IndexQuery indexQuery = new IndexQuery(); sampleEntity2.setMessage("some other message");
indexQuery.setId(documentId); operations.save(sampleEntity2);
indexQuery.setObject(sampleEntity);
operations.index(indexQuery, index);
indexOperations.refresh(); indexOperations.refresh();
// when
CriteriaQuery criteriaQuery = new CriteriaQuery( CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("message").contains("test").and("message").contains("some")); new Criteria("message").contains("test").and("message").contains("some"));
SearchHit<SampleEntity> searchHit = operations.searchOne(criteriaQuery, SampleEntity.class, index);
// when
SearchHit<SampleEntity> sampleEntity1 = operations.searchOne(criteriaQuery, SampleEntity.class, index);
// then // then
assertThat(sampleEntity1).isNotNull(); assertThat(searchHit).isNotNull();
assertThat(searchHit.getId()).isEqualTo(sampleEntity1.id);
} }
// @Ignore("DATAES-30") @Test // ,DATAES-706
@Test public void shouldPerformOrOperationOnCriteriaEntries() {
public void shouldPerformOrOperation() {
// given // given
List<IndexQuery> indexQueries = new ArrayList<>();
// first document
String documentId = nextIdAsString();
SampleEntity sampleEntity1 = new SampleEntity(); SampleEntity sampleEntity1 = new SampleEntity();
sampleEntity1.setId(documentId); sampleEntity1.setId(nextIdAsString());
sampleEntity1.setMessage("some message"); sampleEntity1.setMessage("some test message");
sampleEntity1.setVersion(System.currentTimeMillis()); operations.save(sampleEntity1);
IndexQuery indexQuery1 = new IndexQuery();
indexQuery1.setId(documentId);
indexQuery1.setObject(sampleEntity1);
indexQueries.add(indexQuery1);
// second document
String documentId2 = nextIdAsString();
SampleEntity sampleEntity2 = new SampleEntity(); SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setId(documentId2); sampleEntity2.setId(nextIdAsString());
sampleEntity2.setMessage("test message"); sampleEntity2.setMessage("some other message");
sampleEntity2.setVersion(System.currentTimeMillis()); operations.save(sampleEntity2);
IndexQuery indexQuery2 = new IndexQuery();
indexQuery2.setId(documentId2);
indexQuery2.setObject(sampleEntity2);
indexQueries.add(indexQuery2);
operations.bulkIndex(indexQueries, index);
indexOperations.refresh(); indexOperations.refresh();
CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("message").contains("some").or("message").contains("test"));
// when // when
CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("message").contains("test").or("message").contains("other"));
SearchHits<SampleEntity> searchHits = operations.search(criteriaQuery, SampleEntity.class, index); SearchHits<SampleEntity> searchHits = operations.search(criteriaQuery, SampleEntity.class, index);
// then // then
assertThat(searchHits).isNotNull(); assertThat(searchHits).isNotNull();
assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); assertThat(searchHits.getSearchHits().stream().map(SearchHit::getId)).containsExactlyInAnyOrder(sampleEntity1.id,
sampleEntity2.id);
} }
@Test @Test // ,DATAES-706
public void shouldPerformAndOperationWithinCriteria() { public void shouldPerformAndOperationWithinCriteria() {
// given // given
List<IndexQuery> indexQueries = new ArrayList<>(); SampleEntity sampleEntity1 = new SampleEntity();
sampleEntity1.setId(nextIdAsString());
// first document sampleEntity1.setMessage("some test message");
String documentId = nextIdAsString(); operations.save(sampleEntity1);
SampleEntity sampleEntity = new SampleEntity(); SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity.setId(documentId); sampleEntity2.setId(nextIdAsString());
sampleEntity.setMessage("some message"); sampleEntity2.setMessage("some other message");
sampleEntity.setVersion(System.currentTimeMillis()); operations.save(sampleEntity2);
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(documentId);
indexQuery.setObject(sampleEntity);
indexQueries.add(indexQuery);
operations.bulkIndex(indexQueries, index);
indexOperations.refresh(); indexOperations.refresh();
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria().and(new Criteria("message").contains("some")));
// when // when
CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("message").contains("test").and(new Criteria("message").contains("some")));
SearchHits<SampleEntity> searchHits = operations.search(criteriaQuery, SampleEntity.class, index); SearchHits<SampleEntity> searchHits = operations.search(criteriaQuery, SampleEntity.class, index);
// then // then
@ -183,34 +156,29 @@ public class CriteriaQueryTests {
assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1);
} }
@Test @Test // ,DATAES-706
public void shouldPerformOrOperationWithinCriteria() { public void shouldPerformOrOperationWithinCriteria() {
// given // given
List<IndexQuery> indexQueries = new ArrayList<>(); SampleEntity sampleEntity1 = new SampleEntity();
sampleEntity1.setId(nextIdAsString());
// first document sampleEntity1.setMessage("some test message");
String documentId = nextIdAsString(); operations.save(sampleEntity1);
SampleEntity sampleEntity = new SampleEntity(); SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity.setId(documentId); sampleEntity2.setId(nextIdAsString());
sampleEntity.setMessage("some message"); sampleEntity2.setMessage("some other message");
sampleEntity.setVersion(System.currentTimeMillis()); operations.save(sampleEntity2);
IndexQuery indexQuery = new IndexQuery();
indexQuery.setId(documentId);
indexQuery.setObject(sampleEntity);
indexQueries.add(indexQuery);
operations.bulkIndex(indexQueries, index);
indexOperations.refresh(); indexOperations.refresh();
CriteriaQuery criteriaQuery = new CriteriaQuery(new Criteria().or(new Criteria("message").contains("some")));
// when // when
CriteriaQuery criteriaQuery = new CriteriaQuery(
new Criteria("message").contains("test").or(new Criteria("message").contains("other")));
SearchHits<SampleEntity> searchHits = operations.search(criteriaQuery, SampleEntity.class, index); SearchHits<SampleEntity> searchHits = operations.search(criteriaQuery, SampleEntity.class, index);
// then // then
assertThat(searchHits).isNotNull(); assertThat(searchHits).isNotNull();
assertThat(searchHits.getTotalHits()).isGreaterThanOrEqualTo(1); assertThat(searchHits.getSearchHits().stream().map(SearchHit::getId)).containsExactlyInAnyOrder(sampleEntity1.id,
sampleEntity2.id);
} }
@Test @Test

View File

@ -78,9 +78,9 @@ public class ReactiveElasticsearchStringQueryUnitTests {
public void bindsSimplePropertyCorrectly() throws Exception { public void bindsSimplePropertyCorrectly() throws Exception {
ReactiveElasticsearchStringQuery elasticsearchStringQuery = createQueryForMethod("findByName", String.class); ReactiveElasticsearchStringQuery elasticsearchStringQuery = createQueryForMethod("findByName", String.class);
StubParameterAccessor accesor = new StubParameterAccessor("Luke"); StubParameterAccessor accessor = new StubParameterAccessor("Luke");
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accesor); org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accessor);
StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }"); StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }");
assertThat(query).isInstanceOf(StringQuery.class); assertThat(query).isInstanceOf(StringQuery.class);
@ -93,9 +93,9 @@ public class ReactiveElasticsearchStringQueryUnitTests {
ReactiveElasticsearchStringQuery elasticsearchStringQuery = createQueryForMethod("findByNameWithExpression", ReactiveElasticsearchStringQuery elasticsearchStringQuery = createQueryForMethod("findByNameWithExpression",
String.class); String.class);
StubParameterAccessor accesor = new StubParameterAccessor("Luke"); StubParameterAccessor accessor = new StubParameterAccessor("Luke");
org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accesor); org.springframework.data.elasticsearch.core.query.Query query = elasticsearchStringQuery.createQuery(accessor);
StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }"); StringQuery reference = new StringQuery("{ 'bool' : { 'must' : { 'term' : { 'name' : 'Luke' } } } }");
assertThat(query).isInstanceOf(StringQuery.class); assertThat(query).isInstanceOf(StringQuery.class);