mirror of
https://github.com/spring-projects/spring-data-elasticsearch.git
synced 2025-07-08 19:42:45 +00:00
Allow for null
and empty
parameters in the MultiField annotation.
Original Pull Request #2960 Closes #2952
This commit is contained in:
parent
d079a59cb4
commit
9149c1bc2e
@ -98,6 +98,7 @@ public class SimpleElasticsearchPersistentProperty extends
|
|||||||
this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType());
|
this.isSeqNoPrimaryTerm = SeqNoPrimaryTerm.class.isAssignableFrom(getRawType());
|
||||||
|
|
||||||
boolean isField = isAnnotationPresent(Field.class);
|
boolean isField = isAnnotationPresent(Field.class);
|
||||||
|
boolean isMultiField = isAnnotationPresent(MultiField.class);
|
||||||
|
|
||||||
if (isVersionProperty() && !getType().equals(Long.class)) {
|
if (isVersionProperty() && !getType().equals(Long.class)) {
|
||||||
throw new MappingException(String.format("Version property %s must be of type Long!", property.getName()));
|
throw new MappingException(String.format("Version property %s must be of type Long!", property.getName()));
|
||||||
@ -109,8 +110,10 @@ public class SimpleElasticsearchPersistentProperty extends
|
|||||||
|
|
||||||
initPropertyValueConverter();
|
initPropertyValueConverter();
|
||||||
|
|
||||||
storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue();
|
storeNullValue = isField ? getRequiredAnnotation(Field.class).storeNullValue()
|
||||||
storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue() : true;
|
: isMultiField && getRequiredAnnotation(MultiField.class).mainField().storeNullValue();
|
||||||
|
storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue()
|
||||||
|
: !isMultiField || getRequiredAnnotation(MultiField.class).mainField().storeEmptyValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.data.elasticsearch;
|
package org.springframework.data.elasticsearch;
|
||||||
|
|
||||||
|
import static co.elastic.clients.elasticsearch._types.query_dsl.QueryBuilders.match;
|
||||||
|
import static java.util.UUID.randomUUID;
|
||||||
import static org.assertj.core.api.Assertions.*;
|
import static org.assertj.core.api.Assertions.*;
|
||||||
import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
|
import static org.springframework.data.elasticsearch.utils.IdGenerator.*;
|
||||||
|
|
||||||
@ -28,6 +30,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.DisplayName;
|
||||||
import org.junit.jupiter.api.Order;
|
import org.junit.jupiter.api.Order;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -37,6 +40,7 @@ import org.springframework.data.elasticsearch.annotations.Field;
|
|||||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||||
import org.springframework.data.elasticsearch.annotations.InnerField;
|
import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||||
import org.springframework.data.elasticsearch.annotations.MultiField;
|
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||||
|
import org.springframework.data.elasticsearch.client.elc.NativeQuery;
|
||||||
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
import org.springframework.data.elasticsearch.core.ElasticsearchOperations;
|
||||||
import org.springframework.data.elasticsearch.core.IndexOperations;
|
import org.springframework.data.elasticsearch.core.IndexOperations;
|
||||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||||
@ -373,6 +377,42 @@ public abstract class NestedObjectIntegrationTests {
|
|||||||
assertThat(books.getSearchHit(0).getContent().getId()).isEqualTo(book2.getId());
|
assertThat(books.getSearchHit(0).getContent().getId()).isEqualTo(book2.getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2952
|
||||||
|
@DisplayName("should handle null and empty field parameters in the mapping process")
|
||||||
|
void shouldSupportMappingNullAndEmptyFieldParameter() {
|
||||||
|
// Given
|
||||||
|
operations.indexOps(MultiFieldWithNullEmptyParameters.class).createWithMapping();
|
||||||
|
List<IndexQuery> indexQueries = new ArrayList<>();
|
||||||
|
MultiFieldWithNullEmptyParameters nullObj = new MultiFieldWithNullEmptyParameters();
|
||||||
|
nullObj.addFieldWithInner(randomUUID().toString());
|
||||||
|
MultiFieldWithNullEmptyParameters objWithValue = new MultiFieldWithNullEmptyParameters();
|
||||||
|
objWithValue.addEmptyField(randomUUID().toString());
|
||||||
|
|
||||||
|
IndexQuery indexQuery1 = new IndexQuery();
|
||||||
|
indexQuery1.setId(nextIdAsString());
|
||||||
|
indexQuery1.setObject(nullObj);
|
||||||
|
indexQueries.add(indexQuery1);
|
||||||
|
|
||||||
|
IndexQuery indexQuery2 = new IndexQuery();
|
||||||
|
indexQuery2.setId(nextIdAsString());
|
||||||
|
indexQuery2.setObject(objWithValue);
|
||||||
|
indexQueries.add(indexQuery2);
|
||||||
|
|
||||||
|
// When
|
||||||
|
operations.bulkIndex(indexQueries, MultiFieldWithNullEmptyParameters.class);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
SearchHits<MultiFieldWithNullEmptyParameters> nullResults = operations.search(
|
||||||
|
NativeQuery.builder().withQuery(match(bm -> bm.field("empty-field").query("EMPTY"))).build(),
|
||||||
|
MultiFieldWithNullEmptyParameters.class);
|
||||||
|
assertThat(nullResults.getSearchHits()).hasSize(1);
|
||||||
|
|
||||||
|
nullResults = operations.search(
|
||||||
|
NativeQuery.builder().withQuery(match(bm -> bm.field("inner-field.prefix").query("EMPTY"))).build(),
|
||||||
|
MultiFieldWithNullEmptyParameters.class);
|
||||||
|
assertThat(nullResults.getSearchHits()).hasSize(1);
|
||||||
|
}
|
||||||
|
|
||||||
@NotNull
|
@NotNull
|
||||||
abstract protected Query getNestedQuery4();
|
abstract protected Query getNestedQuery4();
|
||||||
|
|
||||||
@ -622,4 +662,40 @@ public abstract class NestedObjectIntegrationTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Document(indexName = "#{@indexNameProvider.indexName()}-multi-field")
|
||||||
|
static class MultiFieldWithNullEmptyParameters {
|
||||||
|
@Nullable
|
||||||
|
@MultiField(mainField = @Field(name = "empty-field", type = FieldType.Keyword, nullValue = "EMPTY",
|
||||||
|
storeNullValue = true)) private List<String> emptyField;
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@MultiField(mainField = @Field(name = "inner-field", type = FieldType.Text, storeNullValue = true),
|
||||||
|
otherFields = { @InnerField(suffix = "prefix", type = FieldType.Keyword,
|
||||||
|
nullValue = "EMPTY") }) private List<String> fieldWithInner;
|
||||||
|
|
||||||
|
public List<String> getEmptyField() {
|
||||||
|
if (emptyField == null) {
|
||||||
|
emptyField = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return emptyField;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addEmptyField(String value) {
|
||||||
|
getEmptyField().add(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getFieldWithInner() {
|
||||||
|
if (fieldWithInner == null) {
|
||||||
|
fieldWithInner = new ArrayList<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return fieldWithInner;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addFieldWithInner(@Nullable String value) {
|
||||||
|
getFieldWithInner().add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1296,6 +1296,38 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
|||||||
assertEquals(expected, mapping, true);
|
assertEquals(expected, mapping, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2952
|
||||||
|
void shouldMapNullityParameters() throws JSONException {
|
||||||
|
// Given
|
||||||
|
String expected = """
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"_class": {
|
||||||
|
"type": "keyword",
|
||||||
|
"index": false,
|
||||||
|
"doc_values": false
|
||||||
|
},
|
||||||
|
"empty-field": {
|
||||||
|
"type": "keyword",
|
||||||
|
"null_value": "EMPTY",
|
||||||
|
"fields": {
|
||||||
|
"suffix": {
|
||||||
|
"type": "keyword",
|
||||||
|
"null_value": "EMPTY_TEXT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// When
|
||||||
|
String result = getMappingBuilder().buildPropertyMapping(MultiFieldWithNullEmptyParameters.class);
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(expected, result, true);
|
||||||
|
}
|
||||||
|
|
||||||
// region entities
|
// region entities
|
||||||
|
|
||||||
@Document(indexName = "ignore-above-index")
|
@Document(indexName = "ignore-above-index")
|
||||||
@ -2570,5 +2602,14 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
|
|||||||
@MultiField(mainField = @Field(type = FieldType.Text, mappedTypeName = "match_only_text"), otherFields = { @InnerField(suffix = "lower_case",
|
@MultiField(mainField = @Field(type = FieldType.Text, mappedTypeName = "match_only_text"), otherFields = { @InnerField(suffix = "lower_case",
|
||||||
type = FieldType.Keyword, normalizer = "lower_case_normalizer", mappedTypeName = "constant_keyword") }) private String description;
|
type = FieldType.Keyword, normalizer = "lower_case_normalizer", mappedTypeName = "constant_keyword") }) private String description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class MultiFieldWithNullEmptyParameters {
|
||||||
|
@Nullable
|
||||||
|
@MultiField(
|
||||||
|
mainField = @Field(name = "empty-field", type = FieldType.Keyword, nullValue = "EMPTY", storeNullValue = true),
|
||||||
|
otherFields = {
|
||||||
|
@InnerField(suffix = "suffix", type = Keyword, nullValue = "EMPTY_TEXT") }) private List<String> emptyField;
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
@ -18,10 +18,14 @@ package org.springframework.data.elasticsearch.core.index;
|
|||||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||||
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
|
import static org.springframework.data.elasticsearch.annotations.FieldType.*;
|
||||||
|
|
||||||
|
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||||
|
import org.springframework.data.elasticsearch.annotations.MultiField;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import reactor.core.scheduler.Schedulers;
|
import reactor.core.scheduler.Schedulers;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.junit.jupiter.api.DisplayName;
|
import org.junit.jupiter.api.DisplayName;
|
||||||
@ -78,6 +82,40 @@ public class ReactiveMappingBuilderUnitTests extends MappingContextBaseTests {
|
|||||||
|
|
||||||
assertEquals(expected, mapping, true);
|
assertEquals(expected, mapping, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test // #2952
|
||||||
|
void shouldMapNullityParameters() throws JSONException {
|
||||||
|
// Given
|
||||||
|
ReactiveMappingBuilder mappingBuilder = getReactiveMappingBuilder();
|
||||||
|
String expected = """
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"_class": {
|
||||||
|
"type": "keyword",
|
||||||
|
"index": false,
|
||||||
|
"doc_values": false
|
||||||
|
},
|
||||||
|
"empty-field": {
|
||||||
|
"type": "keyword",
|
||||||
|
"null_value": "EMPTY",
|
||||||
|
"fields": {
|
||||||
|
"suffix": {
|
||||||
|
"type": "keyword",
|
||||||
|
"null_value": "EMPTY_TEXT"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""";
|
||||||
|
|
||||||
|
// When
|
||||||
|
String result = Mono.defer(() -> mappingBuilder.buildReactivePropertyMapping(MultiFieldWithNullEmptyParameters.class))
|
||||||
|
.subscribeOn(Schedulers.parallel()).block();
|
||||||
|
|
||||||
|
// Then
|
||||||
|
assertEquals(expected, result, true);
|
||||||
|
}
|
||||||
|
|
||||||
// region entities
|
// region entities
|
||||||
@Document(indexName = "runtime-fields")
|
@Document(indexName = "runtime-fields")
|
||||||
@ -88,5 +126,14 @@ public class ReactiveMappingBuilderUnitTests extends MappingContextBaseTests {
|
|||||||
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp")
|
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp")
|
||||||
@Nullable private Instant timestamp;
|
@Nullable private Instant timestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
private static class MultiFieldWithNullEmptyParameters {
|
||||||
|
@Nullable
|
||||||
|
@MultiField(
|
||||||
|
mainField = @Field(name = "empty-field", type = FieldType.Keyword, nullValue = "EMPTY", storeNullValue = true),
|
||||||
|
otherFields = {
|
||||||
|
@InnerField(suffix = "suffix", type = Keyword, nullValue = "EMPTY_TEXT") }) private List<String> emptyField;
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user