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());
|
||||
|
||||
boolean isField = isAnnotationPresent(Field.class);
|
||||
boolean isMultiField = isAnnotationPresent(MultiField.class);
|
||||
|
||||
if (isVersionProperty() && !getType().equals(Long.class)) {
|
||||
throw new MappingException(String.format("Version property %s must be of type Long!", property.getName()));
|
||||
@ -109,8 +110,10 @@ public class SimpleElasticsearchPersistentProperty extends
|
||||
|
||||
initPropertyValueConverter();
|
||||
|
||||
storeNullValue = isField && getRequiredAnnotation(Field.class).storeNullValue();
|
||||
storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue() : true;
|
||||
storeNullValue = isField ? getRequiredAnnotation(Field.class).storeNullValue()
|
||||
: isMultiField && getRequiredAnnotation(MultiField.class).mainField().storeNullValue();
|
||||
storeEmptyValue = isField ? getRequiredAnnotation(Field.class).storeEmptyValue()
|
||||
: !isMultiField || getRequiredAnnotation(MultiField.class).mainField().storeEmptyValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
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.springframework.data.elasticsearch.utils.IdGenerator.*;
|
||||
|
||||
@ -28,6 +30,7 @@ import java.util.Map;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
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;
|
||||
@ -37,6 +40,7 @@ import org.springframework.data.elasticsearch.annotations.Field;
|
||||
import org.springframework.data.elasticsearch.annotations.FieldType;
|
||||
import org.springframework.data.elasticsearch.annotations.InnerField;
|
||||
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.IndexOperations;
|
||||
import org.springframework.data.elasticsearch.core.SearchHits;
|
||||
@ -373,6 +377,42 @@ public abstract class NestedObjectIntegrationTests {
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
@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
|
||||
|
||||
@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",
|
||||
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
|
||||
}
|
||||
|
@ -18,10 +18,14 @@ package org.springframework.data.elasticsearch.core.index;
|
||||
import static org.skyscreamer.jsonassert.JSONAssert.*;
|
||||
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.scheduler.Schedulers;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
@ -79,6 +83,40 @@ public class ReactiveMappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
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
|
||||
@Document(indexName = "runtime-fields")
|
||||
@Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json")
|
||||
@ -88,5 +126,14 @@ public class ReactiveMappingBuilderUnitTests extends MappingContextBaseTests {
|
||||
@Field(type = Date, format = DateFormat.epoch_millis, name = "@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
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user