Skip to content

Commit dc5bf5a

Browse files
authored
Add the filter parts of a CriteriaQuery to the query, not as post-filter.
Original Pull Request #2906 Closes #2857
1 parent 1d89054 commit dc5bf5a

File tree

6 files changed

+77
-59
lines changed

6 files changed

+77
-59
lines changed

src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaFilterProcessor.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,13 @@ public static Optional<Query> createQuery(Criteria criteria) {
7070
for (Criteria chainedCriteria : criteria.getCriteriaChain()) {
7171

7272
if (chainedCriteria.isOr()) {
73-
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
74-
queriesForEntries(chainedCriteria).forEach(boolQueryBuilder::should);
75-
filterQueries.add(new Query(boolQueryBuilder.build()));
73+
Collection<? extends Query> queriesForEntries = queriesForEntries(chainedCriteria);
74+
75+
if (!queriesForEntries.isEmpty()) {
76+
BoolQuery.Builder boolQueryBuilder = QueryBuilders.bool();
77+
queriesForEntries.forEach(boolQueryBuilder::should);
78+
filterQueries.add(new Query(boolQueryBuilder.build()));
79+
}
7680
} else if (chainedCriteria.isNegating()) {
7781

7882
Assert.notNull(criteria.getField(), "criteria must have a field");

src/main/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryProcessor.java

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.util.ArrayList;
3030
import java.util.Iterator;
3131
import java.util.List;
32+
import java.util.Objects;
3233

3334
import org.springframework.data.elasticsearch.annotations.FieldType;
3435
import org.springframework.data.elasticsearch.core.query.Criteria;
@@ -115,11 +116,18 @@ public static Query createQuery(Criteria criteria) {
115116
}
116117
}
117118

119+
var filterQuery = CriteriaFilterProcessor.createQuery(criteria);
118120
if (shouldQueries.isEmpty() && mustNotQueries.isEmpty() && mustQueries.isEmpty()) {
119-
return null;
121+
122+
if (filterQuery.isEmpty()) {
123+
return null;
124+
}
125+
126+
// we need something to add the filter to
127+
mustQueries.add(Query.of(qb -> qb.matchAll(m -> m)));
120128
}
121129

122-
Query query = new Query.Builder().bool(boolQueryBuilder -> {
130+
return new Query.Builder().bool(boolQueryBuilder -> {
123131

124132
if (!shouldQueries.isEmpty()) {
125133
boolQueryBuilder.should(shouldQueries);
@@ -133,10 +141,10 @@ public static Query createQuery(Criteria criteria) {
133141
boolQueryBuilder.must(mustQueries);
134142
}
135143

144+
filterQuery.ifPresent(boolQueryBuilder::filter);
145+
136146
return boolQueryBuilder;
137147
}).build();
138-
139-
return query;
140148
}
141149

142150
@Nullable
@@ -238,7 +246,7 @@ private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field,
238246
queryBuilder.queryString(queryStringQuery(fieldName, '*' + searchText, true, boost));
239247
break;
240248
case EXPRESSION:
241-
queryBuilder.queryString(queryStringQuery(fieldName, value.toString(), boost));
249+
queryBuilder.queryString(queryStringQuery(fieldName, Objects.requireNonNull(value).toString(), boost));
242250
break;
243251
case LESS:
244252
queryBuilder //
@@ -270,6 +278,7 @@ private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field,
270278
break;
271279
case BETWEEN:
272280
Object[] ranges = (Object[]) value;
281+
Assert.notNull(value, "value for a between condition must not be null");
273282
queryBuilder //
274283
.range(rb -> {
275284
rb.field(fieldName);
@@ -293,10 +302,10 @@ private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field,
293302
.boost(boost)); //
294303
break;
295304
case MATCHES:
296-
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.Or, boost));
305+
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.Or, boost));
297306
break;
298307
case MATCHES_ALL:
299-
queryBuilder.match(matchQuery(fieldName, value.toString(), Operator.And, boost));
308+
queryBuilder.match(matchQuery(fieldName, Objects.requireNonNull(value).toString(), Operator.And, boost));
300309

301310
break;
302311
case IN:
@@ -345,7 +354,7 @@ private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field,
345354
queryBuilder //
346355
.regexp(rb -> rb //
347356
.field(fieldName) //
348-
.value(value.toString()) //
357+
.value(Objects.requireNonNull(value).toString()) //
349358
.boost(boost)); //
350359
break;
351360
case HAS_CHILD:

src/main/java/org/springframework/data/elasticsearch/client/elc/RequestConverter.java

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1210,7 +1210,7 @@ public <T> SearchRequest searchRequest(Query query, @Nullable String routing, @N
12101210
builder.routing(routing);
12111211
}
12121212

1213-
addFilter(query, builder);
1213+
addPostFilter(query, builder);
12141214

12151215
return builder.build();
12161216
}
@@ -1758,22 +1758,18 @@ co.elastic.clients.elasticsearch._types.query_dsl.Query getQuery(@Nullable Query
17581758
return getEsQuery(query, (q) -> elasticsearchConverter.updateQuery(q, clazz));
17591759
}
17601760

1761-
private void addFilter(Query query, SearchRequest.Builder builder) {
1761+
@SuppressWarnings("StatementWithEmptyBody")
1762+
private void addPostFilter(Query query, SearchRequest.Builder builder) {
17621763

1763-
if (query instanceof CriteriaQuery) {
1764-
CriteriaFilterProcessor.createQuery(((CriteriaQuery) query).getCriteria()).ifPresent(builder::postFilter);
1765-
} else // noinspection StatementWithEmptyBody
1766-
if (query instanceof StringQuery) {
1767-
// no filter for StringQuery
1768-
} else if (query instanceof NativeQuery nativeQuery) {
1764+
// we only need to handle NativeQuery here. filter from a CriteriaQuery are added into the query and not as post
1765+
// filter anymore, StringQuery do not have post filters
1766+
if (query instanceof NativeQuery nativeQuery) {
17691767

17701768
if (nativeQuery.getFilter() != null) {
17711769
builder.postFilter(nativeQuery.getFilter());
17721770
} else if (nativeQuery.getSpringDataQuery() != null) {
1773-
addFilter(nativeQuery.getSpringDataQuery(), builder);
1771+
addPostFilter(nativeQuery.getSpringDataQuery(), builder);
17741772
}
1775-
} else {
1776-
throw new IllegalArgumentException("unhandled Query implementation " + query.getClass().getName());
17771773
}
17781774
}
17791775

src/test/java/org/springframework/data/elasticsearch/core/query/NativeQueryIntegrationTests.java

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@
1717

1818
import static org.assertj.core.api.Assertions.*;
1919

20+
import co.elastic.clients.elasticsearch._types.GeoHashPrecision;
21+
import co.elastic.clients.elasticsearch._types.aggregations.Aggregation;
22+
23+
import java.util.List;
24+
2025
import org.junit.jupiter.api.BeforeEach;
2126
import org.junit.jupiter.api.DisplayName;
2227
import org.junit.jupiter.api.Order;
@@ -26,6 +31,7 @@
2631
import org.springframework.data.elasticsearch.annotations.Document;
2732
import org.springframework.data.elasticsearch.annotations.Field;
2833
import org.springframework.data.elasticsearch.annotations.FieldType;
34+
import org.springframework.data.elasticsearch.client.elc.ElasticsearchAggregation;
2935
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
3036
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
3137
import org.springframework.data.elasticsearch.core.geo.GeoPoint;
@@ -98,6 +104,40 @@ void shouldBeAbleToUseCriteriaQueryWithFilterArgumentsInANativeQuery() {
98104
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity1.getId());
99105
}
100106

107+
@Test // #2857
108+
@DisplayName("should apply CriteriaQuery with filter arguments in a NativeQuery to aggregations")
109+
void shouldBeAbleToUseCriteriaQueryWithFilterArgumentsInANativeQueryToAggregations() {
110+
var entity1 = new SampleEntity();
111+
entity1.setId("60");
112+
var location1 = new GeoPoint(60.0, 60.0);
113+
entity1.setLocation(location1);
114+
entity1.setText("60");
115+
var entity2 = new SampleEntity();
116+
entity2.setId("70");
117+
entity2.setText("70");
118+
var location70 = new GeoPoint(70.0, 70.0);
119+
entity2.setLocation(location70);
120+
operations.save(entity1, entity2);
121+
122+
var criteriaQuery = new CriteriaQuery(Criteria.where("location").within(location1, "10km"));
123+
var nativeQuery = NativeQuery.builder()
124+
.withQuery(criteriaQuery)
125+
.withAggregation("geohashgrid", Aggregation.of(ab -> ab
126+
.geohashGrid(ghg -> ghg
127+
.field("location")
128+
.precision(GeoHashPrecision.of(ghp -> ghp.distance("10000km"))))))
129+
.build();
130+
131+
var searchHits = operations.search(nativeQuery, SampleEntity.class);
132+
133+
assertThat(searchHits.getTotalHits()).isEqualTo(1);
134+
assertThat(searchHits.getSearchHit(0).getId()).isEqualTo(entity1.getId());
135+
assertThat(searchHits.getAggregations()).isNotNull();
136+
// noinspection unchecked
137+
var aggregations = (List<ElasticsearchAggregation>) searchHits.getAggregations().aggregations();
138+
assertThat(aggregations).hasSize(1);
139+
}
140+
101141
@Test // #2391
102142
@DisplayName("should be able to use StringQuery in a NativeQuery")
103143
void shouldBeAbleToUseStringQueryInANativeQuery() {

src/test/java/org/springframework/data/elasticsearch/repository/query/keywords/QueryKeywordsIntegrationTests.java

Lines changed: 0 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -79,47 +79,27 @@ void cleanup() {
7979
@Test
8080
public void shouldSupportAND() {
8181

82-
// given
83-
84-
// when
85-
86-
// then
8782
assertThat(repository.findByNameAndText("Sugar", "Cane sugar")).hasSize(2);
8883
assertThat(repository.findByNameAndPrice("Sugar", 1.1f)).hasSize(1);
8984
}
9085

9186
@Test
9287
public void shouldSupportOR() {
9388

94-
// given
95-
96-
// when
97-
98-
// then
9989
assertThat(repository.findByNameOrPrice("Sugar", 1.9f)).hasSize(4);
10090
assertThat(repository.findByNameOrText("Salt", "Beet sugar")).hasSize(3);
10191
}
10292

10393
@Test
10494
public void shouldSupportTrueAndFalse() {
10595

106-
// given
107-
108-
// when
109-
110-
// then
11196
assertThat(repository.findByAvailableTrue()).hasSize(3);
11297
assertThat(repository.findByAvailableFalse()).hasSize(4);
11398
}
11499

115100
@Test
116101
public void shouldSupportInAndNotInAndNot() {
117102

118-
// given
119-
120-
// when
121-
122-
// then
123103
assertThat(repository.findByPriceIn(Arrays.asList(1.2f, 1.1f))).hasSize(2);
124104
assertThat(repository.findByPriceNotIn(Arrays.asList(1.2f, 1.1f))).hasSize(5);
125105
assertThat(repository.findByPriceNot(1.2f)).hasSize(6);
@@ -128,33 +108,18 @@ public void shouldSupportInAndNotInAndNot() {
128108
@Test // DATAES-171
129109
public void shouldWorkWithNotIn() {
130110

131-
// given
132-
133-
// when
134-
135-
// then
136111
assertThat(repository.findByIdNotIn(Arrays.asList("2", "3"))).hasSize(5);
137112
}
138113

139114
@Test
140115
public void shouldSupportBetween() {
141116

142-
// given
143-
144-
// when
145-
146-
// then
147117
assertThat(repository.findByPriceBetween(1.0f, 2.0f)).hasSize(4);
148118
}
149119

150120
@Test
151121
public void shouldSupportLessThanAndGreaterThan() {
152122

153-
// given
154-
155-
// when
156-
157-
// then
158123
assertThat(repository.findByPriceLessThan(1.1f)).hasSize(1);
159124
assertThat(repository.findByPriceLessThanEqual(1.1f)).hasSize(2);
160125

src/test/java/org/springframework/data/elasticsearch/utils/IndexNameProvider.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
package org.springframework.data.elasticsearch.utils;
1717

1818
/**
19-
* Class providing an index name with a prefix and an index number
19+
* Class providing an index name with a prefix, an index number and a random 6-digit number.
2020
*
2121
* @author Peter-Josef Meisch
2222
*/
@@ -35,7 +35,7 @@ public IndexNameProvider(String prefix) {
3535
}
3636

3737
public void increment() {
38-
indexName = prefix + '-' + ++idx;
38+
indexName = prefix + '-' + ++idx + '-' + sixDigits();
3939
}
4040

4141
public String indexName() {
@@ -48,4 +48,8 @@ public String indexName() {
4848
public String getPrefix() {
4949
return prefix;
5050
}
51+
52+
private String sixDigits() {
53+
return String.valueOf((int) (100000 + Math.random() * 900000));
54+
}
5155
}

0 commit comments

Comments
 (0)