Move dynamic mapping parameter configuration to @Document and @Field.

Original Pull Request #1872
Closes #1871
This commit is contained in:
Sascha Woo 2021-07-20 07:51:39 +02:00 committed by GitHub
parent 3b921b7454
commit f74dd879df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 233 additions and 14 deletions

View File

@ -33,6 +33,7 @@ import org.springframework.data.annotation.Persistent;
* @author Ivan Greene
* @author Mark Paluch
* @author Peter-Josef Meisch
* @author Sascha Woo
*/
@Persistent
@Inherited
@ -112,4 +113,12 @@ public @interface Document {
* @since 4.3
*/
WriteTypeHint writeTypeHint() default WriteTypeHint.DEFAULT;
/**
* Controls how Elasticsearch dynamically adds fields to the document.
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
}

View File

@ -0,0 +1,50 @@
/*
* 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.annotations;
/**
* Values for the {@code dynamic} mapping parameter.
*
* @author Sascha Woo
* @since 4.3
*/
public enum Dynamic {
/**
* New fields are added to the mapping.
*/
TRUE,
/**
* New fields are added to the mapping as
* <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime.html">runtime fields</a>. These
* fields are not indexed, and are loaded from {@code _source} at query time.
*/
RUNTIME,
/**
* New fields are ignored. These fields will not be indexed or searchable, but will still appear in the
* {@code _source} field of returned hits. These fields will not be added to the mapping, and new fields must be added
* explicitly.
*/
FALSE,
/**
* If new fields are detected, an exception is thrown and the document is rejected. New fields must be explicitly
* added to the mapping.
*/
STRICT,
/**
* Inherit the dynamic setting from their parent object or from the mapping type.
*/
INHERIT
}

View File

@ -26,11 +26,14 @@ import java.lang.annotation.Target;
* {@see <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic.html">elasticsearch doc</a>}
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.FIELD })
@Documented
@Deprecated
public @interface DynamicMapping {
DynamicMappingValue value() default DynamicMappingValue.True;

View File

@ -19,8 +19,11 @@ package org.springframework.data.elasticsearch.annotations;
* values for the {@link DynamicMapping annotation}
*
* @author Peter-Josef Meisch
* @author Sascha Woo
* @since 4.0
* @deprecated since 4.3, use {@link Document#dynamic()} or {@link Field#dynamic()} instead.
*/
@Deprecated
public enum DynamicMappingValue {
True, False, Strict
}

View File

@ -195,4 +195,12 @@ public @interface Field {
* @since 4.2
*/
int dims() default -1;
/**
* Controls how Elasticsearch dynamically adds fields to the inner object within the document.<br>
* To be used in combination with {@link FieldType#Object} or {@link FieldType#Nested}
*
* @since 4.3
*/
Dynamic dynamic() default Dynamic.INHERIT;
}

View File

@ -197,7 +197,9 @@ public class MappingBuilder {
}
}
if (dynamicMapping != null) {
if (entity != null && entity.dynamic() != Dynamic.INHERIT) {
builder.field(TYPE_DYNAMIC, entity.dynamic().name().toLowerCase());
} else if (dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
}
@ -440,8 +442,12 @@ public class MappingBuilder {
builder.startObject(property.getFieldName());
if (nestedOrObjectField && dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
if (nestedOrObjectField) {
if (annotation.dynamic() != Dynamic.INHERIT) {
builder.field(TYPE_DYNAMIC, annotation.dynamic().name().toLowerCase());
} else if (dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
}
}
addFieldMappingParameters(builder, annotation, nestedOrObjectField);
@ -489,8 +495,12 @@ public class MappingBuilder {
// main field
builder.startObject(property.getFieldName());
if (nestedOrObjectField && dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
if (nestedOrObjectField) {
if (annotation.mainField().dynamic() != Dynamic.INHERIT) {
builder.field(TYPE_DYNAMIC, annotation.mainField().dynamic().name().toLowerCase());
} else if (dynamicMapping != null) {
builder.field(TYPE_DYNAMIC, dynamicMapping.value().name().toLowerCase());
}
}
addFieldMappingParameters(builder, annotation.mainField(), nestedOrObjectField);

View File

@ -16,6 +16,7 @@
package org.springframework.data.elasticsearch.core.mapping;
import org.elasticsearch.index.VersionType;
import org.springframework.data.elasticsearch.annotations.Dynamic;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.core.index.Settings;
import org.springframework.data.elasticsearch.core.join.JoinField;
@ -160,4 +161,10 @@ public interface ElasticsearchPersistentEntity<T> extends PersistentEntity<T, El
* @since 4.3
*/
boolean writeTypeHints();
/**
* @return the {@code dynamic} mapping parameter value.
* @since 4.3
*/
Dynamic dynamic();
}

View File

@ -25,6 +25,7 @@ import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Dynamic;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import org.springframework.data.elasticsearch.annotations.Routing;
@ -73,6 +74,7 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
private @Nullable ElasticsearchPersistentProperty joinFieldProperty;
private @Nullable VersionType versionType;
private boolean createIndexAndMapping;
private final Dynamic dynamic;
private final Map<String, ElasticsearchPersistentProperty> fieldNamePropertyCache = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, Expression> routingExpressions = new ConcurrentHashMap<>();
private @Nullable String routing;
@ -102,8 +104,10 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
this.indexName = document.indexName();
this.versionType = document.versionType();
this.createIndexAndMapping = document.createIndex();
this.dynamic = document.dynamic();
} else {
this.dynamic = Dynamic.INHERIT;
}
Routing routingAnnotation = AnnotatedElementUtils.findMergedAnnotation(clazz, Routing.class);
if (routingAnnotation != null) {
@ -559,4 +563,9 @@ public class SimpleElasticsearchPersistentEntity<T> extends BasicPersistentEntit
return fieldNamingStrategy;
}
}
@Override
public Dynamic dynamic() {
return dynamic;
}
}

View File

@ -287,8 +287,18 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
@Test // #1767
@DisplayName("should write dynamic mapping entries")
void shouldWriteDynamicMappingEntries() {
@DisplayName("should write dynamic mapping annotations")
void shouldWriteDynamicMappingAnnotations() {
IndexOperations indexOps = operations.indexOps(DynamicMappingAnnotationEntity.class);
indexOps.create();
indexOps.putMapping();
}
@Test // #1871
@DisplayName("should write dynamic mapping")
void shouldWriteDynamicMapping() {
IndexOperations indexOps = operations.indexOps(DynamicMappingEntity.class);
indexOps.create();
@ -1104,9 +1114,9 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "dynamic-mapping")
@Document(indexName = "dynamic-mapping-annotation")
@DynamicMapping(DynamicMappingValue.False)
static class DynamicMappingEntity {
static class DynamicMappingAnnotationEntity {
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
@ -1124,6 +1134,31 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "dynamic-mapping", dynamic = Dynamic.FALSE)
static class DynamicMappingEntity {
@Nullable @Field(type = FieldType.Object) //
private Map<String, Object> objectInherit;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
private Map<String, Object> objectFalse;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
private Map<String, Object> objectTrue;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
private Map<String, Object> objectStrict;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
private Map<String, Object> objectRuntime;
@Nullable @Field(type = FieldType.Nested) //
private List<Map<String, Object>> nestedObjectInherit;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
private List<Map<String, Object>> nestedObjectFalse;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
private List<Map<String, Object>> nestedObjectTrue;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
private List<Map<String, Object>> nestedObjectStrict;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
private List<Map<String, Object>> nestedObjectRuntime;
}
@Document(indexName = "dynamic-detection-mapping-true")
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE,
dynamicDateFormats = { "MM/dd/yyyy" })

View File

@ -419,7 +419,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
}
@Test // DATAES-148, #1767
void shouldWriteDynamicMappingSettings() throws JSONException {
void shouldWriteDynamicMappingFromAnnotation() throws JSONException {
String expected = "{\n" + //
" \"dynamic\": \"false\",\n" + //
@ -451,7 +451,65 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
" }\n" + //
"}"; //
String mapping = getMappingBuilder().buildPropertyMapping(ConfigureDynamicMappingEntity.class);
String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingAnnotationEntity.class);
assertEquals(expected, mapping, true);
}
@Test // #1871
void shouldWriteDynamicMapping() throws JSONException {
String expected = "{\n" //
+ " \"dynamic\": \"false\",\n" //
+ " \"properties\": {\n" //
+ " \"_class\": {\n" //
+ " \"type\": \"keyword\",\n" //
+ " \"index\": false,\n" //
+ " \"doc_values\": false\n" //
+ " },\n" //
+ " \"objectInherit\": {\n" //
+ " \"type\": \"object\"\n" //
+ " },\n" //
+ " \"objectFalse\": {\n" //
+ " \"dynamic\": \"false\",\n" //
+ " \"type\": \"object\"\n" //
+ " },\n" //
+ " \"objectTrue\": {\n" //
+ " \"dynamic\": \"true\",\n" //
+ " \"type\": \"object\"\n" //
+ " },\n" //
+ " \"objectStrict\": {\n" //
+ " \"dynamic\": \"strict\",\n" //
+ " \"type\": \"object\"\n" //
+ " },\n" //
+ " \"objectRuntime\": {\n" //
+ " \"dynamic\": \"runtime\",\n" //
+ " \"type\": \"object\"\n" //
+ " },\n" //
+ " \"nestedObjectInherit\": {\n" //
+ " \"type\": \"nested\"\n" //
+ " },\n" //
+ " \"nestedObjectFalse\": {\n" //
+ " \"dynamic\": \"false\",\n" //
+ " \"type\": \"nested\"\n" //
+ " },\n" //
+ " \"nestedObjectTrue\": {\n" //
+ " \"dynamic\": \"true\",\n" //
+ " \"type\": \"nested\"\n" //
+ " },\n" //
+ " \"nestedObjectStrict\": {\n" //
+ " \"dynamic\": \"strict\",\n" //
+ " \"type\": \"nested\"\n" //
+ " },\n" //
+ " \"nestedObjectRuntime\": {\n" //
+ " \"dynamic\": \"runtime\",\n" //
+ " \"type\": \"nested\"\n" //
+ " }\n" //
+ " }\n" //
+ "}\n" //
+ "";
String mapping = getMappingBuilder().buildPropertyMapping(DynamicMappingEntity.class);
assertEquals(expected, mapping, true);
}
@ -865,7 +923,8 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
" \"day_of_week\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"script\": {\n" + //
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + //
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n"
+ //
" }\n" + //
" }\n" + //
" },\n" + //
@ -1441,7 +1500,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
@Document(indexName = "test-index-configure-dynamic-mapping")
@DynamicMapping(DynamicMappingValue.False)
static class ConfigureDynamicMappingEntity {
static class DynamicMappingAnnotationEntity {
@Nullable @DynamicMapping(DynamicMappingValue.Strict) @Field(type = FieldType.Object) private Author author;
@Nullable @DynamicMapping(DynamicMappingValue.False) @Field(
@ -1459,6 +1518,32 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
}
}
@Document(indexName = "test-index-configure-dynamic-mapping", dynamic = Dynamic.FALSE)
static class DynamicMappingEntity {
@Nullable @Field(type = FieldType.Object) //
private Map<String, Object> objectInherit;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.FALSE) //
private Map<String, Object> objectFalse;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.TRUE) //
private Map<String, Object> objectTrue;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.STRICT) //
private Map<String, Object> objectStrict;
@Nullable @Field(type = FieldType.Object, dynamic = Dynamic.RUNTIME) //
private Map<String, Object> objectRuntime;
@Nullable @Field(type = FieldType.Nested) //
private List<Map<String, Object>> nestedObjectInherit;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.FALSE) //
private List<Map<String, Object>> nestedObjectFalse;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.TRUE) //
private List<Map<String, Object>> nestedObjectTrue;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.STRICT) //
private List<Map<String, Object>> nestedObjectStrict;
@Nullable @Field(type = FieldType.Nested, dynamic = Dynamic.RUNTIME) //
private List<Map<String, Object>> nestedObjectRuntime;
}
static class ValueObject {
private final String value;