Add Query by Example feature.

Original Pull Request #2422
Closes #2418
This commit is contained in:
ezequielantunez 2023-01-11 21:50:19 +01:00 committed by GitHub
parent 44a5c7545f
commit 5a36f5e1e8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
16 changed files with 1951 additions and 6 deletions

View File

@ -39,6 +39,7 @@ import org.springframework.util.Assert;
* query. * query.
* *
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Ezequiel Antúnez Camacho
* @since 4.4 * @since 4.4
*/ */
class CriteriaQueryProcessor { class CriteriaQueryProcessor {
@ -329,6 +330,13 @@ class CriteriaQueryProcessor {
throw new CriteriaQueryException("value for " + fieldName + " is not an Iterable"); throw new CriteriaQueryException("value for " + fieldName + " is not an Iterable");
} }
break; break;
case REGEXP:
queryBuilder //
.regexp(rb -> rb //
.field(fieldName) //
.value(value.toString()) //
.boost(boost)); //
break;
default: default:
throw new CriteriaQueryException("Could not build query for " + entry); throw new CriteriaQueryException("Could not build query for " + entry);
} }

View File

@ -44,6 +44,7 @@ import org.springframework.util.Assert;
* @author Rasmus Faber-Espensen * @author Rasmus Faber-Espensen
* @author James Bodkin * @author James Bodkin
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Ezequiel Antúnez Camacho
* @deprecated since 5.0 * @deprecated since 5.0
*/ */
@Deprecated @Deprecated
@ -248,6 +249,9 @@ class CriteriaQueryProcessor {
} }
} }
break; break;
case REGEXP:
query = regexpQuery(fieldName, value.toString());
break;
} }
return query; return query;
} }

View File

@ -50,6 +50,7 @@ import org.springframework.util.StringUtils;
* @author Mohsin Husen * @author Mohsin Husen
* @author Franck Marchand * @author Franck Marchand
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Ezequiel Antúnez Camacho
*/ */
public class Criteria { public class Criteria {
@ -611,6 +612,21 @@ public class Criteria {
return this; return this;
} }
/**
* Add a {@link OperationKey#REGEXP} entry to the {@link #queryCriteriaEntries}.
*
* @param value the regexp value to match
* @return this object
* @since 5.1
*/
public Criteria regexp(String value) {
Assert.notNull(value, "value must not be null");
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.REGEXP, value));
return this;
}
// endregion // endregion
// region criteria entries - filter // region criteria entries - filter
@ -954,7 +970,11 @@ public class Criteria {
/** /**
* @since 4.3 * @since 4.3
*/ */
NOT_EMPTY; NOT_EMPTY, //
/**
* @since 5.1
*/
REGEXP;
/** /**
* @return true if this key does not have an associated value * @return true if this key does not have an associated value

View File

@ -15,22 +15,21 @@
*/ */
package org.springframework.data.elasticsearch.repository.support; package org.springframework.data.elasticsearch.repository.support;
import static org.springframework.data.querydsl.QuerydslUtils.*;
import java.lang.reflect.Method;
import java.util.Optional;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations; import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery; import org.springframework.data.elasticsearch.repository.query.ElasticsearchPartQuery;
import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod; import org.springframework.data.elasticsearch.repository.query.ElasticsearchQueryMethod;
import org.springframework.data.elasticsearch.repository.query.ElasticsearchStringQuery; import org.springframework.data.elasticsearch.repository.query.ElasticsearchStringQuery;
import org.springframework.data.elasticsearch.repository.support.querybyexample.QueryByExampleElasticsearchExecutor;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.QuerydslPredicateExecutor; import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFactorySupport; import org.springframework.data.repository.core.support.RepositoryFactorySupport;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
@ -38,6 +37,11 @@ import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
import org.springframework.util.Assert; import org.springframework.util.Assert;
import java.lang.reflect.Method;
import java.util.Optional;
import static org.springframework.data.querydsl.QuerydslUtils.QUERY_DSL_PRESENT;
/** /**
* Factory to create {@link ElasticsearchRepository} * Factory to create {@link ElasticsearchRepository}
* *
@ -49,6 +53,7 @@ import org.springframework.util.Assert;
* @author Christoph Strobl * @author Christoph Strobl
* @author Sascha Woo * @author Sascha Woo
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Ezequiel Antúnez Camacho
*/ */
public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport { public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
@ -122,4 +127,17 @@ public class ElasticsearchRepositoryFactory extends RepositoryFactorySupport {
protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) { protected RepositoryMetadata getRepositoryMetadata(Class<?> repositoryInterface) {
return new ElasticsearchRepositoryMetadata(repositoryInterface); return new ElasticsearchRepositoryMetadata(repositoryInterface);
} }
@Override
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryComposition.RepositoryFragments fragments = RepositoryComposition.RepositoryFragments.empty();
if (QueryByExampleExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
fragments = fragments.append(RepositoryFragment.implemented(QueryByExampleExecutor.class,
instantiateClass(QueryByExampleElasticsearchExecutor.class, elasticsearchOperations)));
}
return fragments;
}
} }

View File

@ -25,15 +25,19 @@ import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersiste
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryMethod; import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchQueryMethod;
import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchStringQuery; import org.springframework.data.elasticsearch.repository.query.ReactiveElasticsearchStringQuery;
import org.springframework.data.elasticsearch.repository.query.ReactivePartTreeElasticsearchQuery; import org.springframework.data.elasticsearch.repository.query.ReactivePartTreeElasticsearchQuery;
import org.springframework.data.elasticsearch.repository.support.querybyexample.ReactiveQueryByExampleElasticsearchExecutor;
import org.springframework.data.mapping.context.MappingContext; import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.projection.ProjectionFactory; import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.repository.core.NamedQueries; import org.springframework.data.repository.core.NamedQueries;
import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.core.RepositoryInformation;
import org.springframework.data.repository.core.RepositoryMetadata; import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport; import org.springframework.data.repository.core.support.ReactiveRepositoryFactorySupport;
import org.springframework.data.repository.core.support.RepositoryComposition;
import org.springframework.data.repository.core.support.RepositoryFragment;
import org.springframework.data.repository.query.QueryLookupStrategy; import org.springframework.data.repository.query.QueryLookupStrategy;
import org.springframework.data.repository.query.QueryLookupStrategy.Key; import org.springframework.data.repository.query.QueryLookupStrategy.Key;
import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider; import org.springframework.data.repository.query.QueryMethodEvaluationContextProvider;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import org.springframework.data.repository.query.RepositoryQuery; import org.springframework.data.repository.query.RepositoryQuery;
import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.lang.Nullable; import org.springframework.lang.Nullable;
@ -45,6 +49,7 @@ import org.springframework.util.Assert;
* *
* @author Christoph Strobl * @author Christoph Strobl
* @author Ivan Greene * @author Ivan Greene
* @author Ezequiel Antúnez Camacho
* @since 3.2 * @since 3.2
*/ */
public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFactorySupport { public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFactorySupport {
@ -168,4 +173,16 @@ public class ReactiveElasticsearchRepositoryFactory extends ReactiveRepositoryFa
} }
} }
} }
@Override
protected RepositoryComposition.RepositoryFragments getRepositoryFragments(RepositoryMetadata metadata) {
RepositoryComposition.RepositoryFragments fragments = RepositoryComposition.RepositoryFragments.empty();
if (ReactiveQueryByExampleExecutor.class.isAssignableFrom(metadata.getRepositoryInterface())) {
fragments = fragments.append(RepositoryFragment.implemented(ReactiveQueryByExampleExecutor.class,
instantiateClass(ReactiveQueryByExampleElasticsearchExecutor.class, operations)));
}
return fragments;
}
} }

View File

@ -0,0 +1,159 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import java.util.Map;
import java.util.Optional;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentEntity;
import org.springframework.data.elasticsearch.core.mapping.ElasticsearchPersistentProperty;
import org.springframework.data.elasticsearch.core.query.Criteria;
import org.springframework.data.mapping.PersistentPropertyAccessor;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.support.ExampleMatcherAccessor;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;
/**
* Maps a {@link Example} to a {@link org.springframework.data.elasticsearch.core.query.Criteria}
*
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
class ExampleCriteriaMapper {
private final MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext;
/**
* Builds a {@link ExampleCriteriaMapper}
*
* @param mappingContext mappingContext to use
*/
ExampleCriteriaMapper(
MappingContext<? extends ElasticsearchPersistentEntity<?>, ElasticsearchPersistentProperty> mappingContext) {
this.mappingContext = mappingContext;
}
<S> Criteria criteria(Example<S> example) {
return buildCriteria(example);
}
private <S> Criteria buildCriteria(Example<S> example) {
final ExampleMatcherAccessor matcherAccessor = new ExampleMatcherAccessor(example.getMatcher());
return applyPropertySpecs(new Criteria(), "", example.getProbe(),
mappingContext.getRequiredPersistentEntity(example.getProbeType()), matcherAccessor,
example.getMatcher().getMatchMode());
}
private Criteria applyPropertySpecs(Criteria criteria, String path, @Nullable Object probe,
ElasticsearchPersistentEntity<?> persistentEntity, ExampleMatcherAccessor exampleSpecAccessor,
ExampleMatcher.MatchMode matchMode) {
if (probe == null) {
return criteria;
}
PersistentPropertyAccessor<?> propertyAccessor = persistentEntity.getPropertyAccessor(probe);
for (ElasticsearchPersistentProperty property : persistentEntity) {
final String propertyName = property.getName();
String propertyPath = StringUtils.hasText(path) ? (path + "." + propertyName) : propertyName;
if (exampleSpecAccessor.isIgnoredPath(propertyPath) || property.isCollectionLike()
|| property.isVersionProperty()) {
continue;
}
Object propertyValue = propertyAccessor.getProperty(property);
if (property.isMap() && propertyValue != null) {
for (Map.Entry<String, Object> entry : ((Map<String, Object>) propertyValue).entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
criteria = applyPropertySpec(propertyPath + "." + key, value, exampleSpecAccessor, property, matchMode,
criteria);
}
continue;
}
criteria = applyPropertySpec(propertyPath, propertyValue, exampleSpecAccessor, property, matchMode, criteria);
}
return criteria;
}
private Criteria applyPropertySpec(String path, Object propertyValue, ExampleMatcherAccessor exampleSpecAccessor,
ElasticsearchPersistentProperty property, ExampleMatcher.MatchMode matchMode, Criteria criteria) {
if (exampleSpecAccessor.isIgnoreCaseForPath(path)) {
throw new InvalidDataAccessApiUsageException(
"Current implementation of Query-by-Example supports only case-sensitive matching.");
}
final Object transformedValue = exampleSpecAccessor.getValueTransformerForPath(path)
.apply(Optional.ofNullable(propertyValue)).orElse(null);
if (transformedValue == null) {
criteria = tryToAppendMustNotSentence(criteria, path, exampleSpecAccessor);
} else {
if (property.isEntity()) {
return applyPropertySpecs(criteria, path, transformedValue,
mappingContext.getRequiredPersistentEntity(property), exampleSpecAccessor, matchMode);
} else {
return applyStringMatcher(applyMatchMode(criteria, path, matchMode), transformedValue,
exampleSpecAccessor.getStringMatcherForPath(path));
}
}
return criteria;
}
private Criteria tryToAppendMustNotSentence(Criteria criteria, String path,
ExampleMatcherAccessor exampleSpecAccessor) {
if (ExampleMatcher.NullHandler.INCLUDE.equals(exampleSpecAccessor.getNullHandler())
|| exampleSpecAccessor.hasPropertySpecifier(path)) {
return criteria.and(path).not().exists();
}
return criteria;
}
private Criteria applyMatchMode(Criteria criteria, String path, ExampleMatcher.MatchMode matchMode) {
if (matchMode == ExampleMatcher.MatchMode.ALL) {
return criteria.and(path);
} else {
return criteria.or(path);
}
}
private Criteria applyStringMatcher(Criteria criteria, Object value, ExampleMatcher.StringMatcher stringMatcher) {
return switch (stringMatcher) {
case DEFAULT -> criteria.is(value);
case EXACT -> criteria.matchesAll(value);
case STARTING -> criteria.startsWith(validateString(value));
case ENDING -> criteria.endsWith(validateString(value));
case CONTAINING -> criteria.contains(validateString(value));
case REGEX -> criteria.regexp(validateString(value));
};
}
private String validateString(Object value) {
if (value instanceof String) {
return value.toString();
}
throw new IllegalArgumentException("This operation requires a String but got " + value.getClass());
}
}

View File

@ -0,0 +1,104 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHitSupport;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.SearchPage;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.repository.query.QueryByExampleExecutor;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
public class QueryByExampleElasticsearchExecutor<T> implements QueryByExampleExecutor<T> {
protected ElasticsearchOperations operations;
protected ExampleCriteriaMapper exampleCriteriaMapper;
public QueryByExampleElasticsearchExecutor(ElasticsearchOperations operations) {
this.operations = operations;
this.exampleCriteriaMapper = new ExampleCriteriaMapper(operations.getElasticsearchConverter().getMappingContext());
}
@Override
public <S extends T> Optional<S> findOne(Example<S> example) {
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withMaxResults(2).build();
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType()));
if (searchHits.getTotalHits() > 1) {
throw new org.springframework.dao.IncorrectResultSizeDataAccessException(1);
}
return Optional.ofNullable(searchHits).filter(SearchHits::hasSearchHits)
.map(result -> (List<S>) SearchHitSupport.unwrapSearchHits(result)).map(s -> s.get(0));
}
@Override
public <S extends T> Iterable<S> findAll(Example<S> example) {
CriteriaQuery criteriaQuery = new CriteriaQuery(exampleCriteriaMapper.criteria(example));
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType()));
return (List<S>) SearchHitSupport.unwrapSearchHits(searchHits);
}
@Override
public <S extends T> Iterable<S> findAll(Example<S> example, Sort sort) {
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withSort(sort).build();
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType()));
return (List<S>) SearchHitSupport.unwrapSearchHits(searchHits);
}
@Override
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
CriteriaQuery criteriaQuery = CriteriaQuery.builder(exampleCriteriaMapper.criteria(example)).withPageable(pageable)
.build();
SearchHits<S> searchHits = operations.search(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType()));
SearchPage<S> page = SearchHitSupport.searchPageFor(searchHits, criteriaQuery.getPageable());
return (Page<S>) SearchHitSupport.unwrapSearchHits(page);
}
@Override
public <S extends T> long count(Example<S> example) {
final CriteriaQuery criteriaQuery = new CriteriaQuery(exampleCriteriaMapper.criteria(example));
return operations.count(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType()));
}
@Override
public <S extends T> boolean exists(Example<S> example) {
return count(example) > 0L;
}
@Override
public <S extends T, R> R findBy(Example<S> example, Function<FluentQuery.FetchableFluentQuery<S>, R> queryFunction) {
throw new UnsupportedOperationException("findBy example and queryFunction is not supported");
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.util.function.Function;
import org.reactivestreams.Publisher;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.query.CriteriaQuery;
import org.springframework.data.repository.query.FluentQuery;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
public class ReactiveQueryByExampleElasticsearchExecutor<T> implements ReactiveQueryByExampleExecutor<T> {
protected ReactiveElasticsearchOperations operations;
protected ExampleCriteriaMapper exampleCriteriaMapper;
public ReactiveQueryByExampleElasticsearchExecutor(ReactiveElasticsearchOperations operations) {
this.operations = operations;
this.exampleCriteriaMapper = new ExampleCriteriaMapper(operations.getElasticsearchConverter().getMappingContext());
}
@Override
public <S extends T> Mono<S> findOne(Example<S> example) {
return Mono.just(example)
.map(e -> CriteriaQuery.builder(exampleCriteriaMapper.criteria(e)).withMaxResults(2).build())
.flatMapMany(criteriaQuery -> operations.search(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType())))
.buffer(2).map(searchHitList -> {
if (searchHitList.size() > 1) {
throw new IncorrectResultSizeDataAccessException(1);
}
return searchHitList.iterator().next();
}).map(SearchHit::getContent).next();
}
@Override
public <S extends T> Flux<S> findAll(Example<S> example) {
return Mono.just(example).map(e -> new CriteriaQuery(exampleCriteriaMapper.criteria(e)))
.flatMapMany(criteriaQuery -> operations.search(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType())))
.map(SearchHit::getContent);
}
@Override
public <S extends T> Flux<S> findAll(Example<S> example, Sort sort) {
return Mono.just(example).map(e -> CriteriaQuery.builder(exampleCriteriaMapper.criteria(e)).withSort(sort).build())
.flatMapMany(criteriaQuery -> operations.search(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType())))
.map(SearchHit::getContent);
}
@Override
public <S extends T> Mono<Long> count(Example<S> example) {
return Mono.just(example).map(e -> new CriteriaQuery(exampleCriteriaMapper.criteria(e)))
.flatMap(criteriaQuery -> operations.count(criteriaQuery, example.getProbeType(),
operations.getIndexCoordinatesFor(example.getProbeType())));
}
@Override
public <S extends T> Mono<Boolean> exists(Example<S> example) {
return count(example).map(count -> count > 0);
}
@Override
public <S extends T, R, P extends Publisher<R>> P findBy(Example<S> example,
Function<FluentQuery.ReactiveFluentQuery<S>, P> queryFunction) {
throw new UnsupportedOperationException("findBy example and queryFunction is not supported");
}
}

View File

@ -28,6 +28,7 @@ import org.springframework.data.elasticsearch.core.query.Criteria;
/** /**
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Ezequiel Antúnez Camacho
*/ */
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
class CriteriaQueryProcessorUnitTests { class CriteriaQueryProcessorUnitTests {
@ -456,4 +457,30 @@ class CriteriaQueryProcessorUnitTests {
assertEquals(expected, queryString, false); assertEquals(expected, queryString, false);
} }
@Test // #2418
void shouldBuildRegexpQuery() throws JSONException {
String expected = """
{
"bool": {
"must": [
{
"regexp": {
"field1": {
"value": "[^abc]"
}
}
}
]
}
}
""";
Criteria criteria = new Criteria("field1").regexp("[^abc]");
var queryString = queryToJson(CriteriaQueryProcessor.createQuery(criteria), mapper);
assertEquals(expected, queryString, false);
}
} }

View File

@ -24,6 +24,7 @@ import org.springframework.data.elasticsearch.core.query.Criteria;
/** /**
* @author Peter-Josef Meisch * @author Peter-Josef Meisch
* @author Ezequiel Antúnez Camacho
*/ */
@SuppressWarnings("ConstantConditions") @SuppressWarnings("ConstantConditions")
class CriteriaQueryProcessorUnitTests { class CriteriaQueryProcessorUnitTests {
@ -447,4 +448,29 @@ class CriteriaQueryProcessorUnitTests {
assertEquals(expected, query, false); assertEquals(expected, query, false);
} }
@Test // #2418
void shouldBuildRegexpQuery() throws JSONException {
String expected = """
{
"bool": {
"must": [
{
"regexp": {
"field1": {
"value": "[^abc]"
}
}
}
]
}
}
""";
Criteria criteria = new Criteria("field1").regexp("[^abc]");
String queryString = queryProcessor.createQuery(criteria).toString();
assertEquals(expected, queryString, false);
}
} }

View File

@ -0,0 +1,44 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
@ContextConfiguration(classes = { QueryByExampleElasticsearchExecutorELCIntegrationTests.Config.class })
public class QueryByExampleElasticsearchExecutorELCIntegrationTests
extends QueryByExampleElasticsearchExecutorIntegrationTests {
@Configuration
@Import({ ElasticsearchTemplateConfiguration.class })
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.repository.support" },
considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("query-by-example-repository");
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
@ContextConfiguration(classes = { QueryByExampleElasticsearchExecutorERHLCIntegrationTests.Config.class })
public class QueryByExampleElasticsearchExecutorERHLCIntegrationTests
extends QueryByExampleElasticsearchExecutorIntegrationTests {
@Configuration
@Import({ ElasticsearchRestTemplateConfiguration.class })
@EnableElasticsearchRepositories(basePackages = { "org.springframework.data.elasticsearch.repository.support" },
considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("query-by-example-repository-es7");
}
}
}

View File

@ -0,0 +1,638 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import org.assertj.core.api.AbstractThrowableAssert;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.repository.query.QueryByExampleExecutor;
import org.springframework.lang.Nullable;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.springframework.data.elasticsearch.utils.IdGenerator.nextIdAsString;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
@SpringIntegrationTest
abstract class QueryByExampleElasticsearchExecutorIntegrationTests {
@Autowired private SampleElasticsearchRepository repository;
@Autowired private ElasticsearchOperations operations;
@Autowired private IndexNameProvider indexNameProvider;
@BeforeEach
void before() {
indexNameProvider.increment();
operations.indexOps(SampleEntity.class).createWithMapping();
}
@Test // #2418
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
}
@Nested
@DisplayName("All QueryByExampleExecutor operations should work")
class QueryByExampleExecutorOperations {
@Test // #2418
void shouldFindOne() {
// given
String documentId = nextIdAsString();
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(documentId);
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
String documentId2 = nextIdAsString();
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(documentId2);
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2));
// when
SampleEntity probe = new SampleEntity();
probe.setDocumentId(documentId2);
Optional<SampleEntity> entityFromElasticSearch = repository.findOne(Example.of(probe));
// then
assertThat(entityFromElasticSearch).contains(sampleEntity2);
}
@Test // #2418
void shouldThrowExceptionIfMoreThanOneResultInFindOne() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2));
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("some message");
final Example<SampleEntity> example = Example.of(probe);
AbstractThrowableAssert<?, ? extends Throwable> assertThatThrownBy = assertThatThrownBy(
() -> repository.findOne(example));
// then
assertThatThrownBy.isInstanceOf(IncorrectResultSizeDataAccessException.class);
}
@Test // #2418
void shouldFindOneWithNestedField() {
// given
SampleEntity.SampleNestedEntity sampleNestedEntity = new SampleEntity.SampleNestedEntity();
sampleNestedEntity.setNestedData("sampleNestedData");
sampleNestedEntity.setAnotherNestedData("sampleAnotherNestedData");
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("some message");
sampleEntity.setSampleNestedEntity(sampleNestedEntity);
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity.SampleNestedEntity sampleNestedEntity2 = new SampleEntity.SampleNestedEntity();
sampleNestedEntity2.setNestedData("sampleNestedData2");
sampleNestedEntity2.setAnotherNestedData("sampleAnotherNestedData2");
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("some message");
sampleEntity2.setSampleNestedEntity(sampleNestedEntity2);
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2));
// when
SampleEntity.SampleNestedEntity sampleNestedEntityProbe = new SampleEntity.SampleNestedEntity();
sampleNestedEntityProbe.setNestedData("sampleNestedData");
SampleEntity probe = new SampleEntity();
probe.setSampleNestedEntity(sampleNestedEntityProbe);
Optional<SampleEntity> entityFromElasticSearch = repository.findOne(Example.of(probe));
// then
assertThat(entityFromElasticSearch).contains(sampleEntity);
}
@Test // #2418
void shouldFindAll() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("hello world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("bye world");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("hello world");
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe));
// then
assertThat(sampleEntities).isNotNull().hasSize(2);
}
@Test // #2418
void shouldFindAllWithSort() {
// given
SampleEntity sampleEntityWithRate11 = new SampleEntity();
sampleEntityWithRate11.setDocumentId(nextIdAsString());
sampleEntityWithRate11.setMessage("hello world");
sampleEntityWithRate11.setRate(11);
sampleEntityWithRate11.setVersion(System.currentTimeMillis());
SampleEntity sampleEntityWithRate13 = new SampleEntity();
sampleEntityWithRate13.setDocumentId(nextIdAsString());
sampleEntityWithRate13.setMessage("hello world");
sampleEntityWithRate13.setRate(13);
sampleEntityWithRate13.setVersion(System.currentTimeMillis());
SampleEntity sampleEntityWithRate22 = new SampleEntity();
sampleEntityWithRate22.setDocumentId(nextIdAsString());
sampleEntityWithRate22.setMessage("hello world");
sampleEntityWithRate22.setRate(22);
sampleEntityWithRate22.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntityWithRate11, sampleEntityWithRate13, sampleEntityWithRate22));
// when
SampleEntity probe = new SampleEntity();
final Iterable<SampleEntity> all = repository.findAll();
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe),
Sort.by(Sort.Direction.DESC, "rate"));
// then
assertThat(sampleEntities).isNotNull().hasSize(3).containsExactly(sampleEntityWithRate22, sampleEntityWithRate13,
sampleEntityWithRate11);
}
@Test // #2418
void shouldFindAllWithPageable() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setRate(1);
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("hello world");
sampleEntity2.setRate(3);
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hello world");
sampleEntity3.setRate(2);
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
// when
SampleEntity probe = new SampleEntity();
Iterable<SampleEntity> page1 = repository.findAll(Example.of(probe),
PageRequest.of(0, 2, Sort.Direction.DESC, "rate"));
Iterable<SampleEntity> page2 = repository.findAll(Example.of(probe),
PageRequest.of(1, 2, Sort.Direction.DESC, "rate"));
// then
assertThat(page1).isNotNull().hasSize(2).containsExactly(sampleEntity2, sampleEntity3);
assertThat(page2).isNotNull().hasSize(1).containsExactly(sampleEntity);
}
@Test // #2418
void shouldCount() {
// given
String documentId = nextIdAsString();
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(documentId);
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
String documentId2 = nextIdAsString();
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(documentId2);
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2));
// when
SampleEntity probe = new SampleEntity();
probe.setDocumentId(documentId2);
final long count = repository.count(Example.of(probe));
// then
assertThat(count).isPositive();
}
@Test // #2418
void shouldExists() {
// given
String documentId = nextIdAsString();
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(documentId);
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
String documentId2 = nextIdAsString();
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(documentId2);
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2));
// when
SampleEntity probe = new SampleEntity();
probe.setDocumentId(documentId2);
boolean exists = repository.exists(Example.of(probe));
// then
assertThat(exists).isTrue();
}
}
@Nested
@DisplayName("All ExampleMatchers should work")
class AllExampleMatchersShouldWork {
@Test // #2418
void defaultStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("hello world");
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.DEFAULT))));
// then
assertThat(sampleEntities).isNotNull().hasSize(1);
}
@Test // #2418
void exactStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("bye world");
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.EXACT))));
// then
assertThat(sampleEntities).isNotNull().hasSize(1);
}
@Test // #2418
void startingStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("h");
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))));
// then
assertThat(sampleEntities).isNotNull().hasSize(2);
}
@Test // #2418
void endingStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("world");
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.ENDING))));
// then
assertThat(sampleEntities).isNotNull().hasSize(2);
}
@Test // #2418
void regexStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3));
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("[(hello)(hola)].*");
Iterable<SampleEntity> sampleEntities = repository.findAll(Example.of(probe, ExampleMatcher.matching()
.withMatcher("message", ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.REGEX))));
// then
assertThat(sampleEntities).isNotNull().hasSize(2);
}
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SampleEntity {
@Nullable
@Id private String documentId;
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
@Nullable
@Field(type = FieldType.Keyword, store = true) private String message;
@Nullable private Integer rate;
@Nullable private Boolean available;
@Nullable
@Field(type = FieldType.Nested, store = true, fielddata = true) private SampleNestedEntity sampleNestedEntity;
@Nullable
@Version private Long version;
@Nullable
public String getDocumentId() {
return documentId;
}
public void setDocumentId(@Nullable String documentId) {
this.documentId = documentId;
}
@Nullable
public String getType() {
return type;
}
public void setType(@Nullable String type) {
this.type = type;
}
@Nullable
public String getMessage() {
return message;
}
public void setMessage(@Nullable String message) {
this.message = message;
}
@Nullable
public Integer getRate() {
return rate;
}
public void setRate(Integer rate) {
this.rate = rate;
}
@Nullable
public Boolean isAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
@Nullable
public SampleNestedEntity getSampleNestedEntity() {
return sampleNestedEntity;
}
public void setSampleNestedEntity(SampleNestedEntity sampleNestedEntity) {
this.sampleNestedEntity = sampleNestedEntity;
}
@Nullable
public java.lang.Long getVersion() {
return version;
}
public void setVersion(@Nullable java.lang.Long version) {
this.version = version;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SampleEntity that = (SampleEntity) o;
if (!Objects.equals(rate, that.rate))
return false;
if (available != that.available)
return false;
if (!Objects.equals(documentId, that.documentId))
return false;
if (!Objects.equals(type, that.type))
return false;
if (!Objects.equals(message, that.message))
return false;
if (!Objects.equals(sampleNestedEntity, that.sampleNestedEntity))
return false;
return Objects.equals(version, that.version);
}
@Override
public int hashCode() {
int result = documentId != null ? documentId.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (message != null ? message.hashCode() : 0);
result = 31 * result + (rate != null ? rate.hashCode() : 0);
result = 31 * result + (available != null ? available.hashCode() : 0);
result = 31 * result + (sampleNestedEntity != null ? sampleNestedEntity.hashCode() : 0);
result = 31 * result + (version != null ? version.hashCode() : 0);
return result;
}
static class SampleNestedEntity {
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true) private String nestedData;
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true) private String anotherNestedData;
@Nullable
public String getNestedData() {
return nestedData;
}
public void setNestedData(@Nullable String nestedData) {
this.nestedData = nestedData;
}
@Nullable
public String getAnotherNestedData() {
return anotherNestedData;
}
public void setAnotherNestedData(@Nullable String anotherNestedData) {
this.anotherNestedData = anotherNestedData;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SampleNestedEntity that = (SampleNestedEntity) o;
return Objects.equals(nestedData, that.nestedData) && Objects.equals(anotherNestedData, that.anotherNestedData);
}
@Override
public int hashCode() {
int result = nestedData != null ? nestedData.hashCode() : 0;
result = 31 * result + (anotherNestedData != null ? anotherNestedData.hashCode() : 0);
return result;
}
}
}
interface SampleElasticsearchRepository
extends ElasticsearchRepository<SampleEntity, String>, QueryByExampleExecutor<SampleEntity> {}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
@ContextConfiguration(classes = { ReactiveQueryByExampleElasticsearchExecutorELCIntegrationTests.Config.class })
public class ReactiveQueryByExampleElasticsearchExecutorELCIntegrationTests
extends ReactiveQueryByExampleElasticsearchExecutorIntegrationTests {
@Configuration
@Import({ ReactiveElasticsearchTemplateConfiguration.class })
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("reactive-query-by-example-repository");
}
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.test.context.ContextConfiguration;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
@ContextConfiguration(classes = { ReactiveQueryByExampleElasticsearchExecutorERHLCIntegrationTests.Config.class })
public class ReactiveQueryByExampleElasticsearchExecutorERHLCIntegrationTests
extends ReactiveQueryByExampleElasticsearchExecutorIntegrationTests {
@Configuration
@Import({ ReactiveElasticsearchRestTemplateConfiguration.class })
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
static class Config {
@Bean
IndexNameProvider indexNameProvider() {
return new IndexNameProvider("reactive-query-by-example-repository-es7");
}
}
}

View File

@ -0,0 +1,652 @@
/*
* Copyright 2023 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.repository.support.querybyexample;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.IncorrectResultSizeDataAccessException;
import org.springframework.data.annotation.Id;
import org.springframework.data.annotation.Version;
import org.springframework.data.domain.Example;
import org.springframework.data.domain.ExampleMatcher;
import org.springframework.data.domain.Sort;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
import org.springframework.lang.Nullable;
import reactor.test.StepVerifier;
import java.util.List;
import java.util.Objects;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.data.elasticsearch.utils.IdGenerator.nextIdAsString;
/**
* @author Ezequiel Antúnez Camacho
* @since 5.1
*/
@SpringIntegrationTest
abstract class ReactiveQueryByExampleElasticsearchExecutorIntegrationTests {
@Autowired private SampleReactiveElasticsearchRepository repository;
@Autowired private ReactiveElasticsearchOperations operations;
@Autowired private IndexNameProvider indexNameProvider;
@BeforeEach
void before() {
indexNameProvider.increment();
operations.indexOps(SampleEntity.class).createWithMapping()
.as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
}
@Test // #2418
@org.junit.jupiter.api.Order(Integer.MAX_VALUE)
void cleanup() {
operations.indexOps(IndexCoordinates.of(indexNameProvider.getPrefix() + "*")).delete();
}
@Nested
@DisplayName("All QueryByExampleExecutor operations should work")
class ReactiveQueryByExampleExecutorOperations {
@Test
void shouldFindOne() {
// given
String documentId = nextIdAsString();
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(documentId);
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
String documentId2 = nextIdAsString();
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(documentId2);
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
.as(StepVerifier::create) //
.expectNextCount(2L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setDocumentId(documentId2);
repository.findOne(Example.of(probe))
// then
.as(StepVerifier::create) //
.consumeNextWith(entityFromElasticSearch -> assertThat(entityFromElasticSearch).isEqualTo(sampleEntity2)) //
.verifyComplete();
}
@Test
void shouldThrowExceptionIfMoreThanOneResultInFindOne() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
.as(StepVerifier::create) //
.expectNextCount(2L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
repository.findOne(Example.of(probe))
// then
.as(StepVerifier::create) //
.expectError(IncorrectResultSizeDataAccessException.class) //
.verify();
}
@Test
void shouldFindOneWithNestedField() {
// given
SampleEntity.SampleNestedEntity sampleNestedEntity = new SampleEntity.SampleNestedEntity();
sampleNestedEntity.setNestedData("sampleNestedData");
sampleNestedEntity.setAnotherNestedData("sampleAnotherNestedData");
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("some message");
sampleEntity.setSampleNestedEntity(sampleNestedEntity);
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity.SampleNestedEntity sampleNestedEntity2 = new SampleEntity.SampleNestedEntity();
sampleNestedEntity2.setNestedData("sampleNestedData2");
sampleNestedEntity2.setAnotherNestedData("sampleAnotherNestedData2");
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("some message");
sampleEntity2.setSampleNestedEntity(sampleNestedEntity2);
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
.as(StepVerifier::create) //
.expectNextCount(2L) //
.verifyComplete();
// when
SampleEntity.SampleNestedEntity sampleNestedEntityProbe = new SampleEntity.SampleNestedEntity();
sampleNestedEntityProbe.setNestedData("sampleNestedData");
SampleEntity probe = new SampleEntity();
probe.setSampleNestedEntity(sampleNestedEntityProbe);
repository.findOne(Example.of(probe))
// then
.as(StepVerifier::create) //
.expectNext(sampleEntity) //
.verifyComplete();
}
@Test
void shouldFindAll() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("hello world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("bye world");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
.as(StepVerifier::create) //
.expectNextCount(3L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("hello world");
repository.findAll(Example.of(probe))
// then
.as(StepVerifier::create) //
.expectNextSequence(List.of(sampleEntity, sampleEntity2)) //
.verifyComplete();
}
@Test
void shouldFindAllWithSort() {
// given
SampleEntity sampleEntityWithRate11 = new SampleEntity();
sampleEntityWithRate11.setDocumentId(nextIdAsString());
sampleEntityWithRate11.setMessage("hello world");
sampleEntityWithRate11.setRate(11);
sampleEntityWithRate11.setVersion(System.currentTimeMillis());
SampleEntity sampleEntityWithRate13 = new SampleEntity();
sampleEntityWithRate13.setDocumentId(nextIdAsString());
sampleEntityWithRate13.setMessage("hello world");
sampleEntityWithRate13.setRate(13);
sampleEntityWithRate13.setVersion(System.currentTimeMillis());
SampleEntity sampleEntityWithRate22 = new SampleEntity();
sampleEntityWithRate22.setDocumentId(nextIdAsString());
sampleEntityWithRate22.setMessage("hello world");
sampleEntityWithRate22.setRate(22);
sampleEntityWithRate22.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntityWithRate11, sampleEntityWithRate13, sampleEntityWithRate22)) //
.as(StepVerifier::create) //
.expectNextCount(3L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
repository.findAll(Example.of(probe), Sort.by(Sort.Direction.DESC, "rate"))
// then
.as(StepVerifier::create) //
.expectNextSequence(List.of(sampleEntityWithRate22, sampleEntityWithRate13, sampleEntityWithRate11)) //
.verifyComplete();
}
@Test
void shouldCount() {
// given
String documentId = nextIdAsString();
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(documentId);
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
String documentId2 = nextIdAsString();
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(documentId2);
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
.as(StepVerifier::create) //
.expectNextCount(2L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setDocumentId(documentId2);
repository.count(Example.of(probe))
// then
.as(StepVerifier::create) //
.expectNext(1L) //
.verifyComplete();
}
@Test
void shouldExists() {
// given
String documentId = nextIdAsString();
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(documentId);
sampleEntity.setMessage("some message");
sampleEntity.setVersion(System.currentTimeMillis());
String documentId2 = nextIdAsString();
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(documentId2);
sampleEntity2.setMessage("some message");
sampleEntity2.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2)) //
.as(StepVerifier::create) //
.expectNextCount(2L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setDocumentId(documentId2);
repository.exists(Example.of(probe))
// then
.as(StepVerifier::create) //
.expectNext(true) //
.verifyComplete();
}
}
@Nested
@DisplayName("All ExampleMatchers should work")
class AllExampleMatchersShouldWork {
@Test // #2418
void defaultStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
.as(StepVerifier::create) //
.expectNextCount(3L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("hello world");
repository
.findAll(Example.of(probe,
ExampleMatcher.matching().withMatcher("message",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.DEFAULT))))
// then
.as(StepVerifier::create) //
.expectNext(sampleEntity) //
.verifyComplete();
}
@Test // #2418
void exactStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
.as(StepVerifier::create) //
.expectNextCount(3L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("bye world");
repository
.findAll(Example.of(probe,
ExampleMatcher.matching().withMatcher("message",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.EXACT))))
// then
.as(StepVerifier::create) //
.expectNext(sampleEntity2) //
.verifyComplete();
}
@Test // #2418
void startingStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
.as(StepVerifier::create) //
.expectNextCount(3L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("h");
repository
.findAll(Example.of(probe,
ExampleMatcher.matching().withMatcher("message",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.STARTING))))
// then
.as(StepVerifier::create) //
.expectNextSequence(List.of(sampleEntity, sampleEntity3)) //
.verifyComplete();
}
@Test // #2418
void endingStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
.as(StepVerifier::create) //
.expectNextCount(3L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("world");
repository
.findAll(Example.of(probe,
ExampleMatcher.matching().withMatcher("message",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.ENDING))))
// then
.as(StepVerifier::create) //
.expectNextSequence(List.of(sampleEntity, sampleEntity2)) //
.verifyComplete();
}
@Test // #2418
void regexStringMatcherShouldWork() {
// given
SampleEntity sampleEntity = new SampleEntity();
sampleEntity.setDocumentId(nextIdAsString());
sampleEntity.setMessage("hello world");
sampleEntity.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity2 = new SampleEntity();
sampleEntity2.setDocumentId(nextIdAsString());
sampleEntity2.setMessage("bye world");
sampleEntity2.setVersion(System.currentTimeMillis());
SampleEntity sampleEntity3 = new SampleEntity();
sampleEntity3.setDocumentId(nextIdAsString());
sampleEntity3.setMessage("hola mundo");
sampleEntity3.setVersion(System.currentTimeMillis());
repository.saveAll(List.of(sampleEntity, sampleEntity2, sampleEntity3)) //
.as(StepVerifier::create) //
.expectNextCount(3L) //
.verifyComplete();
// when
SampleEntity probe = new SampleEntity();
probe.setMessage("[(hello)(hola)].*");
repository
.findAll(Example.of(probe,
ExampleMatcher.matching().withMatcher("message",
ExampleMatcher.GenericPropertyMatcher.of(ExampleMatcher.StringMatcher.REGEX))))
// then
.as(StepVerifier::create) //
.expectNextSequence(List.of(sampleEntity, sampleEntity3)) //
.verifyComplete();
}
}
@Document(indexName = "#{@indexNameProvider.indexName()}")
static class SampleEntity {
@Nullable
@Id private String documentId;
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true) private String type;
@Nullable
@Field(type = FieldType.Keyword, store = true) private String message;
@Nullable private Integer rate;
@Nullable private Boolean available;
@Nullable
@Field(type = FieldType.Nested, store = true,
fielddata = true) private SampleEntity.SampleNestedEntity sampleNestedEntity;
@Nullable
@Version private Long version;
@Nullable
public String getDocumentId() {
return documentId;
}
public void setDocumentId(@Nullable String documentId) {
this.documentId = documentId;
}
@Nullable
public String getType() {
return type;
}
public void setType(@Nullable String type) {
this.type = type;
}
@Nullable
public String getMessage() {
return message;
}
public void setMessage(@Nullable String message) {
this.message = message;
}
@Nullable
public Integer getRate() {
return rate;
}
public void setRate(Integer rate) {
this.rate = rate;
}
@Nullable
public Boolean isAvailable() {
return available;
}
public void setAvailable(Boolean available) {
this.available = available;
}
@Nullable
public SampleEntity.SampleNestedEntity getSampleNestedEntity() {
return sampleNestedEntity;
}
public void setSampleNestedEntity(SampleEntity.SampleNestedEntity sampleNestedEntity) {
this.sampleNestedEntity = sampleNestedEntity;
}
@Nullable
public java.lang.Long getVersion() {
return version;
}
public void setVersion(@Nullable java.lang.Long version) {
this.version = version;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SampleEntity that = (SampleEntity) o;
if (!Objects.equals(rate, that.rate))
return false;
if (available != that.available)
return false;
if (!Objects.equals(documentId, that.documentId))
return false;
if (!Objects.equals(type, that.type))
return false;
if (!Objects.equals(message, that.message))
return false;
if (!Objects.equals(sampleNestedEntity, that.sampleNestedEntity))
return false;
return Objects.equals(version, that.version);
}
@Override
public int hashCode() {
int result = documentId != null ? documentId.hashCode() : 0;
result = 31 * result + (type != null ? type.hashCode() : 0);
result = 31 * result + (message != null ? message.hashCode() : 0);
result = 31 * result + (rate != null ? rate.hashCode() : 0);
result = 31 * result + (available != null ? available.hashCode() : 0);
result = 31 * result + (sampleNestedEntity != null ? sampleNestedEntity.hashCode() : 0);
result = 31 * result + (version != null ? version.hashCode() : 0);
return result;
}
static class SampleNestedEntity {
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true) private String nestedData;
@Nullable
@Field(type = FieldType.Text, store = true, fielddata = true) private String anotherNestedData;
@Nullable
public String getNestedData() {
return nestedData;
}
public void setNestedData(@Nullable String nestedData) {
this.nestedData = nestedData;
}
@Nullable
public String getAnotherNestedData() {
return anotherNestedData;
}
public void setAnotherNestedData(@Nullable String anotherNestedData) {
this.anotherNestedData = anotherNestedData;
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SampleEntity.SampleNestedEntity that = (SampleEntity.SampleNestedEntity) o;
return Objects.equals(nestedData, that.nestedData) && Objects.equals(anotherNestedData, that.anotherNestedData);
}
@Override
public int hashCode() {
int result = nestedData != null ? nestedData.hashCode() : 0;
result = 31 * result + (anotherNestedData != null ? anotherNestedData.hashCode() : 0);
return result;
}
}
}
interface SampleReactiveElasticsearchRepository
extends ReactiveElasticsearchRepository<SampleEntity, String>, ReactiveQueryByExampleExecutor<SampleEntity> {}
}