mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-08 21:22:12 +00:00
parent
44a5c7545f
commit
5a36f5e1e8
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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> {}
|
||||||
|
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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> {}
|
||||||
|
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user