Skip to content

Commit bbadfe9

Browse files
Add Query by Example feature #2418
Fixed Criteria.regexp Minor fixes in QueryByExample fragments and mapper Improved test coverage
1 parent 0b36a26 commit bbadfe9

14 files changed

+904
-501
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ private static Query.Builder queryFor(Criteria.CriteriaEntry entry, Field field,
334334
queryBuilder //
335335
.regexp(rb -> rb //
336336
.field(fieldName) //
337-
.value(searchText) //
337+
.value(value.toString()) //
338338
.boost(boost)); //
339339
break;
340340
default:

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ private QueryBuilder queryFor(Criteria.CriteriaEntry entry, Field field) {
250250
}
251251
break;
252252
case REGEXP:
253-
query = regexpQuery(fieldName, searchText);
253+
query = regexpQuery(fieldName, value.toString());
254254
break;
255255
}
256256
return query;

src/main/java/org/springframework/data/elasticsearch/core/query/Criteria.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -617,8 +617,9 @@ public Criteria notEmpty() {
617617
*
618618
* @param value the regexp value to match
619619
* @return this object
620+
* @since 5.1
620621
*/
621-
public Criteria regexp(Object value) {
622+
public Criteria regexp(String value) {
622623

623624
Assert.notNull(value, "value must not be null");
624625

@@ -970,6 +971,9 @@ public enum OperationKey { //
970971
* @since 4.3
971972
*/
972973
NOT_EMPTY, //
974+
/**
975+
* @since 5.1
976+
*/
973977
REGEXP;
974978

975979
/**

src/main/java/org/springframework/data/elasticsearch/repository/support/querybyexample/ExampleCriteriaMapper.java

Lines changed: 15 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@
1515
*/
1616
package org.springframework.data.elasticsearch.repository.support.querybyexample;
1717

18+
import java.util.Map;
19+
import java.util.Optional;
20+
1821
import org.springframework.dao.InvalidDataAccessApiUsageException;
1922
import org.springframework.data.domain.Example;
2023
import org.springframework.data.domain.ExampleMatcher;
@@ -27,40 +30,31 @@
2730
import org.springframework.lang.Nullable;
2831
import org.springframework.util.StringUtils;
2932

30-
import java.util.EnumSet;
31-
import java.util.Map;
32-
import java.util.Optional;
33-
import java.util.Set;
34-
3533
/**
3634
* Maps a {@link Example} to a {@link org.springframework.data.elasticsearch.core.query.Criteria}
3735
*
38-
* @param <T> Class type
3936
* @author Ezequiel Antúnez Camacho
37+
* @since 5.1
4038
*/
41-
class ExampleCriteriaMapper<T> {
39+
class ExampleCriteriaMapper {
4240

43-
private static final Set<ExampleMatcher.StringMatcher> SUPPORTED_MATCHERS = EnumSet.of(
44-
ExampleMatcher.StringMatcher.DEFAULT, ExampleMatcher.StringMatcher.EXACT, ExampleMatcher.StringMatcher.STARTING,
45-
ExampleMatcher.StringMatcher.CONTAINING, ExampleMatcher.StringMatcher.ENDING);
46-
47-
private final MappingContext<? extends ElasticsearchPersistentEntity<T>, ElasticsearchPersistentProperty> mappingContext;
41+
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
4842

4943
/**
5044
* Builds a {@link ExampleCriteriaMapper}
5145
*
5246
* @param mappingContext mappingContext to use
5347
*/
5448
ExampleCriteriaMapper(
55-
MappingContext<? extends ElasticsearchPersistentEntity<T>, ElasticsearchPersistentProperty> mappingContext) {
49+
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
5650
this.mappingContext = mappingContext;
5751
}
5852

59-
<S extends T> Criteria criteria(Example<S> example) {
53+
<S> Criteria criteria(Example<S> example) {
6054
return buildCriteria(example);
6155
}
6256

63-
private <S extends T> Criteria buildCriteria(Example<S> example) {
57+
private <S> Criteria buildCriteria(Example<S> example) {
6458
final ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
6559

6660
return applyPropertySpecs(new Criteria(), "", example.getProbe(),
@@ -79,7 +73,7 @@ private Criteria applyPropertySpecs(Criteria criteria, String path, @Nullable Ob
7973
PersistentPropertyAccessor<?> propertyAccessor = persistentEntity.getPropertyAccessor(probe);
8074

8175
for (ElasticsearchPersistentProperty property : persistentEntity) {
82-
final String propertyName = getPropertyName(property);
76+
final String propertyName = property.getName();
8377
String propertyPath = StringUtils.hasText(path) ? (path + "." + propertyName) : propertyName;
8478
if (exampleSpecAccessor.isIgnoredPath(propertyPath) || property.isCollectionLike()
8579
|| property.isVersionProperty()) {
@@ -102,10 +96,6 @@ private Criteria applyPropertySpecs(Criteria criteria, String path, @Nullable Ob
10296
return criteria;
10397
}
10498

105-
private String getPropertyName(ElasticsearchPersistentProperty property) {
106-
return property.isIdProperty() ? "_id" : property.getName();
107-
}
108-
10999
private Criteria applyPropertySpec(String path, Object propertyValue, ExampleMatcherAccessor exampleSpecAccessor,
110100
ElasticsearchPersistentProperty property, ExampleMatcher.MatchMode matchMode, Criteria criteria) {
111101

@@ -114,13 +104,6 @@ private Criteria applyPropertySpec(String path, Object propertyValue, ExampleMat
114104
"Current implementation of Query-by-Example supports only case-sensitive matching.");
115105
}
116106

117-
ExampleMatcher.StringMatcher stringMatcher = exampleSpecAccessor.getStringMatcherForPath(path);
118-
if (!SUPPORTED_MATCHERS.contains(exampleSpecAccessor.getStringMatcherForPath(path))) {
119-
throw new InvalidDataAccessApiUsageException(String.format(
120-
"Current implementation of Query-by-Example does not support string matcher %s. Supported matchers are: %s.",
121-
stringMatcher, SUPPORTED_MATCHERS));
122-
}
123-
124107
final Object transformedValue = exampleSpecAccessor.getValueTransformerForPath(path)
125108
.apply(Optional.ofNullable(propertyValue)).orElse(null);
126109

@@ -131,7 +114,8 @@ private Criteria applyPropertySpec(String path, Object propertyValue, ExampleMat
131114
return applyPropertySpecs(criteria, path, transformedValue,
132115
mappingContext.getRequiredPersistentEntity(property), exampleSpecAccessor, matchMode);
133116
} else {
134-
return applyStringMatcher(applyMatchMode(criteria, path, matchMode), transformedValue, stringMatcher);
117+
return applyStringMatcher(applyMatchMode(criteria, path, matchMode), transformedValue,
118+
exampleSpecAccessor.getStringMatcherForPath(path));
135119
}
136120
}
137121
return criteria;
@@ -156,11 +140,12 @@ private Criteria applyMatchMode(Criteria criteria, String path, ExampleMatcher.M
156140

157141
private Criteria applyStringMatcher(Criteria criteria, Object value, ExampleMatcher.StringMatcher stringMatcher) {
158142
return switch (stringMatcher) {
159-
case DEFAULT, EXACT -> criteria.is(value);
143+
case DEFAULT -> criteria.is(value);
144+
case EXACT -> criteria.matchesAll(value);
160145
case STARTING -> criteria.startsWith(validateString(value));
161146
case ENDING -> criteria.endsWith(validateString(value));
162147
case CONTAINING -> criteria.contains(validateString(value));
163-
case REGEX -> throw new UnsupportedOperationException("REGEX matcher is unsupported");
148+
case REGEX -> criteria.regexp(validateString(value));
164149
};
165150
}
166151

src/main/java/org/springframework/data/elasticsearch/repository/support/querybyexample/QueryByExampleElasticsearchExecutor.java

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package org.springframework.data.elasticsearch.repository.support.querybyexample;
1717

18+
import java.util.List;
19+
import java.util.Optional;
20+
import java.util.function.Function;
21+
1822
import org.springframework.data.domain.Example;
1923
import org.springframework.data.domain.Page;
2024
import org.springframework.data.domain.Pageable;
@@ -23,35 +27,27 @@
2327
import org.springframework.data.elasticsearch.core.SearchHitSupport;
2428
import org.springframework.data.elasticsearch.core.SearchHits;
2529
import org.springframework.data.elasticsearch.core.SearchPage;
26-
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
27-
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
2830
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
29-
import org.springframework.data.mapping.context.MappingContext;
3031
import org.springframework.data.repository.query.FluentQuery;
3132
import org.springframework.data.repository.query.QueryByExampleExecutor;
3233

33-
import java.util.List;
34-
import java.util.Optional;
35-
import java.util.function.Function;
36-
3734
/**
3835
* @author Ezequiel Antúnez Camacho
36+
* @since 5.1
3937
*/
4038
public class QueryByExampleElasticsearchExecutor<T> implements QueryByExampleExecutor<T> {
4139

4240
protected ElasticsearchOperations operations;
43-
protected ExampleCriteriaMapper<T> exampleCriteriaMapper;
41+
protected ExampleCriteriaMapper exampleCriteriaMapper;
4442

4543
public QueryByExampleElasticsearchExecutor(ElasticsearchOperations operations) {
4644
this.operations = operations;
47-
this.exampleCriteriaMapper = new ExampleCriteriaMapper<>(
48-
(MappingContext<? extends ElasticsearchPersistentEntity<T>, ElasticsearchPersistentProperty>) operations
49-
.getElasticsearchConverter().getMappingContext());
45+
this.exampleCriteriaMapper = new ExampleCriteriaMapper(operations.getElasticsearchConverter().getMappingContext());
5046
}
5147

5248
@Override
5349
public <S extends T> Optional<S> findOne(Example<S> example) {
54-
CriteriaQuery criteriaQuery = new CriteriaQuery(exampleCriteriaMapper.criteria(example));
50+
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withMaxResults(2).build();
5551
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
5652
operations.getIndexCoordinatesFor(example.getProbeType()));
5753
if (searchHits.getTotalHits() > 1) {

src/main/java/org/springframework/data/elasticsearch/repository/support/querybyexample/ReactiveQueryByExampleElasticsearchExecutor.java

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,36 +15,33 @@
1515
*/
1616
package org.springframework.data.elasticsearch.repository.support.querybyexample;
1717

18+
import reactor.core.publisher.Flux;
19+
import reactor.core.publisher.Mono;
20+
21+
import java.util.function.Function;
22+
1823
import org.reactivestreams.Publisher;
1924
import org.springframework.dao.IncorrectResultSizeDataAccessException;
2025
import org.springframework.data.domain.Example;
2126
import org.springframework.data.domain.Sort;
2227
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
2328
import org.springframework.data.elasticsearch.core.SearchHit;
24-
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
25-
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
2629
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
27-
import org.springframework.data.mapping.context.MappingContext;
2830
import org.springframework.data.repository.query.FluentQuery;
2931
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
30-
import reactor.core.publisher.Flux;
31-
import reactor.core.publisher.Mono;
32-
33-
import java.util.function.Function;
3432

3533
/**
3634
* @author Ezequiel Antúnez Camacho
35+
* @since 5.1
3736
*/
3837
public class ReactiveQueryByExampleElasticsearchExecutor<T> implements ReactiveQueryByExampleExecutor<T> {
3938

4039
protected ReactiveElasticsearchOperations operations;
41-
protected ExampleCriteriaMapper<T> exampleCriteriaMapper;
40+
protected ExampleCriteriaMapper exampleCriteriaMapper;
4241

4342
public ReactiveQueryByExampleElasticsearchExecutor(ReactiveElasticsearchOperations operations) {
4443
this.operations = operations;
45-
this.exampleCriteriaMapper = new ExampleCriteriaMapper<>(
46-
(MappingContext<? extends ElasticsearchPersistentEntity<T>, ElasticsearchPersistentProperty>) operations
47-
.getElasticsearchConverter().getMappingContext());
44+
this.exampleCriteriaMapper = new ExampleCriteriaMapper(operations.getElasticsearchConverter().getMappingContext());
4845
}
4946

5047
@Override

src/test/java/org/springframework/data/elasticsearch/client/elc/CriteriaQueryProcessorUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -467,7 +467,7 @@ void shouldBuildRegexpQuery() throws JSONException {
467467
{
468468
"regexp": {
469469
"field1": {
470-
"value": "\\\\[\\\\^abc\\\\]"
470+
"value": "[^abc]"
471471
}
472472
}
473473
}

src/test/java/org/springframework/data/elasticsearch/client/erhlc/CriteriaQueryProcessorUnitTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,7 +458,7 @@ void shouldBuildRegexpQuery() throws JSONException {
458458
{
459459
"regexp": {
460460
"field1": {
461-
"value": "\\\\[\\\\^abc\\\\]"
461+
"value": "[^abc]"
462462
}
463463
}
464464
}

src/test/java/org/springframework/data/elasticsearch/repository/support/querybyexample/QueryByExampleElasticsearchExecutorELCIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
/**
2727
* @author Ezequiel Antúnez Camacho
28+
* @since 5.1
2829
*/
2930
@ContextConfiguration(classes = { QueryByExampleElasticsearchExecutorELCIntegrationTests.Config.class })
3031
public class QueryByExampleElasticsearchExecutorELCIntegrationTests
@@ -39,6 +40,5 @@ static class Config {
3940
IndexNameProvider indexNameProvider() {
4041
return new IndexNameProvider("query-by-example-repository");
4142
}
42-
4343
}
4444
}

src/test/java/org/springframework/data/elasticsearch/repository/support/querybyexample/QueryByExampleElasticsearchExecutorERHLCIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
/**
2727
* @author Ezequiel Antúnez Camacho
28+
* @since 5.1
2829
*/
2930
@ContextConfiguration(classes = { QueryByExampleElasticsearchExecutorERHLCIntegrationTests.Config.class })
3031
public class QueryByExampleElasticsearchExecutorERHLCIntegrationTests
@@ -39,6 +40,5 @@ static class Config {
3940
IndexNameProvider indexNameProvider() {
4041
return new IndexNameProvider("query-by-example-repository-es7");
4142
}
42-
4343
}
4444
}

0 commit comments

Comments
 (0)