mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-06-28 14:52:20 +00:00
Add repository search for nullable or empty properties.
Original Pull Request #1946 Closes #1909
This commit is contained in:
parent
b8ae9b4a83
commit
175e7b51ae
@ -242,10 +242,6 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
| `findByNameNotIn(Collection<String>names)`
|
||||
| `{"query": {"bool": {"must": [{"query_string": {"query": "NOT(\"?\" \"?\")", "fields": ["name"]}}]}}}`
|
||||
|
||||
| `Near`
|
||||
| `findByStoreNear`
|
||||
| `Not Supported Yet !`
|
||||
|
||||
| `True`
|
||||
| `findByAvailableTrue`
|
||||
| `{ "query" : {
|
||||
@ -277,6 +273,26 @@ A list of supported keywords for Elasticsearch is shown below.
|
||||
}, "sort":[{"name":{"order":"desc"}}]
|
||||
}`
|
||||
|
||||
| `Exists`
|
||||
| `findByNameExists`
|
||||
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
|
||||
|
||||
| `IsNull`
|
||||
| `findByNameIsNull`
|
||||
| `{"query":{"bool":{"must_not":[{"exists":{"field":"name"}}]}}}`
|
||||
|
||||
| `IsNotNull`
|
||||
| `findByNameIsNotNull`
|
||||
| `{"query":{"bool":{"must":[{"exists":{"field":"name"}}]}}}`
|
||||
|
||||
| `IsEmpty`
|
||||
| `findByNameIsEmpty`
|
||||
| `{"query":{"bool":{"must":[{"bool":{"must":[{"exists":{"field":"name"}}],"must_not":[{"wildcard":{"name":{"wildcard":"*"}}}]}}]}}}`
|
||||
|
||||
| `IsNotEmpty`
|
||||
| `findByNameIsNotEmpty`
|
||||
| `{"query":{"bool":{"must":[{"wildcard":{"name":{"wildcard":"*"}}}]}}}`
|
||||
|
||||
|===
|
||||
|
||||
NOTE: Methods names to build Geo-shape queries taking `GeoJson` parameters are not supported.
|
||||
|
@ -165,20 +165,35 @@ class CriteriaQueryProcessor {
|
||||
@Nullable
|
||||
private QueryBuilder queryFor(Criteria.CriteriaEntry entry, Field field) {
|
||||
|
||||
QueryBuilder query = null;
|
||||
String fieldName = field.getName();
|
||||
boolean isKeywordField = FieldType.Keyword == field.getFieldType();
|
||||
|
||||
OperationKey key = entry.getKey();
|
||||
|
||||
if (key == OperationKey.EXISTS) {
|
||||
return existsQuery(fieldName);
|
||||
// operations without a value
|
||||
switch (key) {
|
||||
case EXISTS:
|
||||
query = existsQuery(fieldName);
|
||||
break;
|
||||
case EMPTY:
|
||||
query = boolQuery().must(existsQuery(fieldName)).mustNot(wildcardQuery(fieldName, "*"));
|
||||
break;
|
||||
case NOT_EMPTY:
|
||||
query = wildcardQuery(fieldName, "*");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (query != null) {
|
||||
return query;
|
||||
}
|
||||
|
||||
// now operation keys with a value
|
||||
Object value = entry.getValue();
|
||||
String searchText = QueryParserUtil.escape(value.toString());
|
||||
|
||||
QueryBuilder query = null;
|
||||
|
||||
switch (key) {
|
||||
case EQUALS:
|
||||
query = queryStringQuery(searchText).field(fieldName).defaultOperator(AND);
|
||||
|
@ -586,6 +586,31 @@ public class Criteria {
|
||||
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.MATCHES_ALL, value));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link OperationKey#EMPTY} entry to the {@link #queryCriteriaEntries}.
|
||||
*
|
||||
* @return this object
|
||||
* @since 4.3
|
||||
*/
|
||||
public Criteria empty() {
|
||||
|
||||
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.EMPTY));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a {@link OperationKey#NOT_EMPTY} entry to the {@link #queryCriteriaEntries}.
|
||||
*
|
||||
* @return this object
|
||||
* @since 4.3
|
||||
*/
|
||||
public Criteria notEmpty() {
|
||||
|
||||
queryCriteriaEntries.add(new CriteriaEntry(OperationKey.NOT_EMPTY));
|
||||
return this;
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region criteria entries - filter
|
||||
@ -921,7 +946,15 @@ public class Criteria {
|
||||
/**
|
||||
* @since 4.1
|
||||
*/
|
||||
GEO_CONTAINS
|
||||
GEO_CONTAINS, //
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
EMPTY, //
|
||||
/**
|
||||
* @since 4.3
|
||||
*/
|
||||
NOT_EMPTY
|
||||
}
|
||||
|
||||
/**
|
||||
@ -934,7 +967,9 @@ public class Criteria {
|
||||
|
||||
protected CriteriaEntry(OperationKey key) {
|
||||
|
||||
Assert.isTrue(key == OperationKey.EXISTS, "key must be OperationKey.EXISTS for this call");
|
||||
boolean keyIsValid = key == OperationKey.EXISTS || key == OperationKey.EMPTY || key == OperationKey.NOT_EMPTY;
|
||||
Assert.isTrue(keyIsValid,
|
||||
"key must be OperationKey.EXISTS, OperationKey.EMPTY or OperationKey.EMPTY for this call");
|
||||
|
||||
this.key = key;
|
||||
}
|
||||
|
@ -186,7 +186,15 @@ public class ElasticsearchQueryCreator extends AbstractQueryCreator<CriteriaQuer
|
||||
if (firstParameter instanceof String && secondParameter instanceof String)
|
||||
return criteria.within((String) firstParameter, (String) secondParameter);
|
||||
}
|
||||
|
||||
case EXISTS:
|
||||
case IS_NOT_NULL:
|
||||
return criteria.exists();
|
||||
case IS_NULL:
|
||||
return criteria.not().exists();
|
||||
case IS_EMPTY:
|
||||
return criteria.empty();
|
||||
case IS_NOT_EMPTY:
|
||||
return criteria.notEmpty();
|
||||
default:
|
||||
throw new InvalidDataAccessApiUsageException("Illegal criteria found '" + type + "'.");
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import org.springframework.data.elasticsearch.core.query.Criteria;
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
class CriteriaQueryProcessorUnitTests {
|
||||
|
||||
private final CriteriaQueryProcessor queryProcessor = new CriteriaQueryProcessor();
|
||||
@ -371,4 +372,67 @@ class CriteriaQueryProcessorUnitTests {
|
||||
|
||||
assertEquals(expected, query, false);
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should build query for empty property")
|
||||
void shouldBuildQueryForEmptyProperty() throws JSONException {
|
||||
|
||||
String expected = "{\n" + //
|
||||
" \"bool\" : {\n" + //
|
||||
" \"must\" : [\n" + //
|
||||
" {\n" + //
|
||||
" \"bool\" : {\n" + //
|
||||
" \"must\" : [\n" + //
|
||||
" {\n" + //
|
||||
" \"exists\" : {\n" + //
|
||||
" \"field\" : \"lastName\"" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" ],\n" + //
|
||||
" \"must_not\" : [\n" + //
|
||||
" {\n" + //
|
||||
" \"wildcard\" : {\n" + //
|
||||
" \"lastName\" : {\n" + //
|
||||
" \"wildcard\" : \"*\"" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" ]\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" ]\n" + //
|
||||
" }\n" + //
|
||||
"}"; //
|
||||
|
||||
Criteria criteria = new Criteria("lastName").empty();
|
||||
|
||||
String query = queryProcessor.createQuery(criteria).toString();
|
||||
|
||||
assertEquals(expected, query, false);
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should build query for non-empty property")
|
||||
void shouldBuildQueryForNonEmptyProperty() throws JSONException {
|
||||
|
||||
String expected = "{\n" + //
|
||||
" \"bool\" : {\n" + //
|
||||
" \"must\" : [\n" + //
|
||||
" {\n" + //
|
||||
" \"wildcard\" : {\n" + //
|
||||
" \"lastName\" : {\n" + //
|
||||
" \"wildcard\" : \"*\"\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" }\n" + //
|
||||
" ]\n" + //
|
||||
" }\n" + //
|
||||
"}\n"; //
|
||||
|
||||
Criteria criteria = new Criteria("lastName").notEmpty();
|
||||
|
||||
String query = queryProcessor.createQuery(criteria).toString();
|
||||
|
||||
assertEquals(expected, query, false);
|
||||
}
|
||||
}
|
||||
|
@ -35,6 +35,7 @@ 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.IndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
|
||||
@ -75,9 +76,10 @@ class QueryKeywordsTests {
|
||||
Product product3 = new Product("3", "Sugar", "Beet sugar", 1.1f, true, "sort3");
|
||||
Product product4 = new Product("4", "Salt", "Rock salt", 1.9f, true, "sort2");
|
||||
Product product5 = new Product("5", "Salt", "Sea salt", 2.1f, false, "sort1");
|
||||
Product product6 = new Product("6", null, "no name", 3.4f, false, "sort0");
|
||||
Product product6 = new Product("6", null, "no name", 3.4f, false, "sort6");
|
||||
Product product7 = new Product("7", "", "empty name", 3.4f, false, "sort7");
|
||||
|
||||
repository.saveAll(Arrays.asList(product1, product2, product3, product4, product5, product6));
|
||||
repository.saveAll(Arrays.asList(product1, product2, product3, product4, product5, product6, product7));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@ -118,7 +120,7 @@ class QueryKeywordsTests {
|
||||
|
||||
// then
|
||||
assertThat(repository.findByAvailableTrue()).hasSize(3);
|
||||
assertThat(repository.findByAvailableFalse()).hasSize(3);
|
||||
assertThat(repository.findByAvailableFalse()).hasSize(4);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -130,8 +132,8 @@ class QueryKeywordsTests {
|
||||
|
||||
// then
|
||||
assertThat(repository.findByPriceIn(Arrays.asList(1.2f, 1.1f))).hasSize(2);
|
||||
assertThat(repository.findByPriceNotIn(Arrays.asList(1.2f, 1.1f))).hasSize(4);
|
||||
assertThat(repository.findByPriceNot(1.2f)).hasSize(5);
|
||||
assertThat(repository.findByPriceNotIn(Arrays.asList(1.2f, 1.1f))).hasSize(5);
|
||||
assertThat(repository.findByPriceNot(1.2f)).hasSize(6);
|
||||
}
|
||||
|
||||
@Test // DATAES-171
|
||||
@ -142,7 +144,7 @@ class QueryKeywordsTests {
|
||||
// when
|
||||
|
||||
// then
|
||||
assertThat(repository.findByIdNotIn(Arrays.asList("2", "3"))).hasSize(4);
|
||||
assertThat(repository.findByIdNotIn(Arrays.asList("2", "3"))).hasSize(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -167,8 +169,8 @@ class QueryKeywordsTests {
|
||||
assertThat(repository.findByPriceLessThan(1.1f)).hasSize(1);
|
||||
assertThat(repository.findByPriceLessThanEqual(1.1f)).hasSize(2);
|
||||
|
||||
assertThat(repository.findByPriceGreaterThan(1.9f)).hasSize(2);
|
||||
assertThat(repository.findByPriceGreaterThanEqual(1.9f)).hasSize(3);
|
||||
assertThat(repository.findByPriceGreaterThan(1.9f)).hasSize(3);
|
||||
assertThat(repository.findByPriceGreaterThanEqual(1.9f)).hasSize(4);
|
||||
}
|
||||
|
||||
@Test // DATAES-615
|
||||
@ -193,7 +195,8 @@ class QueryKeywordsTests {
|
||||
List<String> sortedIds = repository.findAllByOrderByText().stream() //
|
||||
.map(it -> it.text).collect(Collectors.toList());
|
||||
|
||||
assertThat(sortedIds).containsExactly("Beet sugar", "Cane sugar", "Cane sugar", "Rock salt", "Sea salt", "no name");
|
||||
assertThat(sortedIds).containsExactly("Beet sugar", "Cane sugar", "Cane sugar", "Rock salt", "Sea salt",
|
||||
"empty name", "no name");
|
||||
}
|
||||
|
||||
@Test // DATAES-615
|
||||
@ -202,7 +205,7 @@ class QueryKeywordsTests {
|
||||
List<String> sortedIds = repository.findAllByOrderBySortName().stream() //
|
||||
.map(it -> it.id).collect(Collectors.toList());
|
||||
|
||||
assertThat(sortedIds).containsExactly("6", "5", "4", "3", "2", "1");
|
||||
assertThat(sortedIds).containsExactly("5", "4", "3", "2", "1", "6", "7");
|
||||
}
|
||||
|
||||
@Test // DATAES-178
|
||||
@ -252,7 +255,7 @@ class QueryKeywordsTests {
|
||||
repository.deleteByName(null);
|
||||
|
||||
long count = repository.count();
|
||||
assertThat(count).isEqualTo(5);
|
||||
assertThat(count).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test // DATAES-937
|
||||
@ -273,6 +276,52 @@ class QueryKeywordsTests {
|
||||
assertThat(products).isEmpty();
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by property exists")
|
||||
void shouldFindByPropertyExists() {
|
||||
|
||||
SearchHits<Product> searchHits = repository.findByNameExists();
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by property is not null")
|
||||
void shouldFindByPropertyIsNotNull() {
|
||||
|
||||
SearchHits<Product> searchHits = repository.findByNameIsNotNull();
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(6);
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by property is null")
|
||||
void shouldFindByPropertyIsNull() {
|
||||
|
||||
SearchHits<Product> searchHits = repository.findByNameIsNull();
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by empty property")
|
||||
void shouldFindByEmptyProperty() {
|
||||
|
||||
SearchHits<Product> searchHits = repository.findByNameEmpty();
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(1);
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by non-empty property")
|
||||
void shouldFindByNonEmptyProperty() {
|
||||
|
||||
SearchHits<Product> searchHits = repository.findByNameNotEmpty();
|
||||
|
||||
assertThat(searchHits.getTotalHits()).isEqualTo(5);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "test-index-product-query-keywords")
|
||||
static class Product {
|
||||
@Nullable @Id private String id;
|
||||
@ -346,6 +395,7 @@ class QueryKeywordsTests {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "SpringDataRepositoryMethodParametersInspection", "SpringDataMethodInconsistencyInspection" })
|
||||
interface ProductRepository extends ElasticsearchRepository<Product, String> {
|
||||
|
||||
List<Product> findByName(@Nullable String name);
|
||||
@ -399,6 +449,16 @@ class QueryKeywordsTests {
|
||||
void deleteByName(@Nullable String name);
|
||||
|
||||
List<Product> findAllByNameIn(List<String> names);
|
||||
|
||||
SearchHits<Product> findByNameExists();
|
||||
|
||||
SearchHits<Product> findByNameIsNull();
|
||||
|
||||
SearchHits<Product> findByNameIsNotNull();
|
||||
|
||||
SearchHits<Product> findByNameEmpty();
|
||||
|
||||
SearchHits<Product> findByNameNotEmpty();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* Copyright 2021 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.query.keywords;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Order;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.annotation.Id;
|
||||
import org.springframework.data.elasticsearch.annotations.Document;
|
||||
import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.core.ReactiveElasticsearchOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHit;
|
||||
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.ReactiveElasticsearchRestTemplateConfiguration;
|
||||
import org.springframework.data.elasticsearch.junit.jupiter.SpringIntegrationTest;
|
||||
import org.springframework.data.elasticsearch.repository.ReactiveElasticsearchRepository;
|
||||
import org.springframework.data.elasticsearch.repository.config.EnableReactiveElasticsearchRepositories;
|
||||
import org.springframework.data.elasticsearch.utils.IndexNameProvider;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.test.context.ContextConfiguration;
|
||||
|
||||
/**
|
||||
* @author Peter-Josef Meisch
|
||||
*/
|
||||
@SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection")
|
||||
@SpringIntegrationTest
|
||||
@ContextConfiguration(classes = { ReactiveQueryKeywordsIntegrationTests.Config.class })
|
||||
public class ReactiveQueryKeywordsIntegrationTests {
|
||||
|
||||
@Configuration
|
||||
@Import({ ReactiveElasticsearchRestTemplateConfiguration.class })
|
||||
@EnableReactiveElasticsearchRepositories(considerNestedRepositories = true)
|
||||
static class Config {
|
||||
@Bean
|
||||
IndexNameProvider indexNameProvider() {
|
||||
return new IndexNameProvider("reactive-template");
|
||||
}
|
||||
}
|
||||
|
||||
@Autowired private IndexNameProvider indexNameProvider;
|
||||
@Autowired private ReactiveElasticsearchOperations operations;
|
||||
@Autowired private SampleRepository repository;
|
||||
|
||||
// region setup
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
indexNameProvider.increment();
|
||||
operations.indexOps(SampleEntity.class).createWithMapping().block();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Order(java.lang.Integer.MAX_VALUE)
|
||||
void cleanup() {
|
||||
operations.indexOps(IndexCoordinates.of("*")).delete().block();
|
||||
}
|
||||
// endregion
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by property exists")
|
||||
void shouldFindByPropertyExists() {
|
||||
|
||||
loadEntities();
|
||||
repository.findByMessageExists().mapNotNull(SearchHit::getId).collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(ids -> { //
|
||||
assertThat(ids).containsExactlyInAnyOrder("empty-message", "with-message"); //
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by property is not null")
|
||||
void shouldFindByPropertyIsNotNull() {
|
||||
|
||||
loadEntities();
|
||||
repository.findByMessageIsNotNull().mapNotNull(SearchHit::getId).collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(ids -> { //
|
||||
assertThat(ids).containsExactlyInAnyOrder("empty-message", "with-message"); //
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by property is null")
|
||||
void shouldFindByPropertyIsNull() {
|
||||
|
||||
loadEntities();
|
||||
repository.findByMessageIsNull().mapNotNull(SearchHit::getId).collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(ids -> { //
|
||||
assertThat(ids).containsExactlyInAnyOrder("null-message"); //
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by empty property ")
|
||||
void shouldFindByEmptyProperty() {
|
||||
|
||||
loadEntities();
|
||||
repository.findByMessageIsEmpty().mapNotNull(SearchHit::getId).collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(ids -> { //
|
||||
assertThat(ids).containsExactlyInAnyOrder("empty-message"); //
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@Test // #1909
|
||||
@DisplayName("should find by not empty property ")
|
||||
void shouldFindByNotEmptyProperty() {
|
||||
|
||||
loadEntities();
|
||||
repository.findByMessageIsNotEmpty().mapNotNull(SearchHit::getId).collectList() //
|
||||
.as(StepVerifier::create) //
|
||||
.assertNext(ids -> { //
|
||||
assertThat(ids).containsExactlyInAnyOrder("with-message"); //
|
||||
}).verifyComplete();
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpringDataMethodInconsistencyInspection")
|
||||
interface SampleRepository extends ReactiveElasticsearchRepository<SampleEntity, String> {
|
||||
Flux<SearchHit<SampleEntity>> findByMessageExists();
|
||||
|
||||
Flux<SearchHit<SampleEntity>> findByMessageIsNotNull();
|
||||
|
||||
Flux<SearchHit<SampleEntity>> findByMessageIsNull();
|
||||
|
||||
Flux<SearchHit<SampleEntity>> findByMessageIsNotEmpty();
|
||||
|
||||
Flux<SearchHit<SampleEntity>> findByMessageIsEmpty();
|
||||
}
|
||||
|
||||
private void loadEntities() {
|
||||
repository.saveAll(Flux.just( //
|
||||
new SampleEntity("with-message", "message"), //
|
||||
new SampleEntity("empty-message", ""), //
|
||||
new SampleEntity("null-message", null)) //
|
||||
).blockLast(); //
|
||||
}
|
||||
|
||||
// region entities
|
||||
@SuppressWarnings("unused")
|
||||
@Document(indexName = "#{@indexNameProvider.indexName()}")
|
||||
static class SampleEntity {
|
||||
@Nullable @Id private String id;
|
||||
|
||||
@Nullable @Field(type = Text) private String message;
|
||||
|
||||
public SampleEntity() {}
|
||||
|
||||
public SampleEntity(@Nullable String id, @Nullable String message) {
|
||||
this.id = id;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(@Nullable String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void setMessage(@Nullable String message) {
|
||||
this.message = message;
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user