datatype detection support in mapping.

Original Pull Request #1810
Closes #638
This commit is contained in:
Peter-Josef Meisch 2021-05-13 10:26:24 +02:00 committed by GitHub
parent df0d65eda2
commit 38b1763b34
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 178 additions and 8 deletions

View File

@ -46,6 +46,16 @@ class Entity {
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
====
[[elasticsearch.misc.mappings]]
== Index Mapping
When Spring Data Elasticsearch creates the index mapping with the `IndexOperations.createMapping()` methods, it uses the annotations described in <<elasticsearch.mapping.meta-model.annotations>>, especially the `@Field` annotation. In addition to that it is possible to add the `@Mapping` annotation to a class. This annotation has the following properties:
* `mappingPath` a classpath resource in JSON format which is used as the mapping, no other mapping processing is done.
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
[[elasticsearch.misc.filter]]
== Filter Builder

View File

@ -9,19 +9,17 @@ import java.lang.annotation.Target;
import org.springframework.data.annotation.Persistent;
/**
* Elasticsearch dynamic templates mapping.
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
* DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
* Elasticsearch dynamic templates mapping. This annotation is handy if you prefer apply dynamic templates on fields
* with annotation e.g. {@link Field} with type = FieldType.Object etc. instead of static mapping on Document via
* {@link Mapping} annotation. DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
*
* @author Petr Kukral
*/
@Persistent
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Target({ ElementType.TYPE })
public @interface DynamicTemplates {
String mappingPath() default "";
}

View File

@ -38,8 +38,31 @@ public @interface Mapping {
/**
* whether mappings are enabled
*
*
* @since 4.2
*/
boolean enabled() default true;
/**
* whether date_detection is enabled
*
* @since 4.3
*/
Detection dateDetection() default Detection.DEFAULT;
/**
* whether numeric_detection is enabled
*
* @since 4.3
*/
Detection numericDetection() default Detection.DEFAULT;
/**
* custom dynamic date formats
* @since 4.3
*/
String[] dynamicDateFormats() default {};
enum Detection {
DEFAULT, TRUE, FALSE;
}
}

View File

@ -92,6 +92,9 @@ public class MappingBuilder {
private static final String JOIN_TYPE_RELATIONS = "relations";
private static final String MAPPING_ENABLED = "enabled";
private static final String DATE_DETECTION = "date_detection";
private static final String NUMERIC_DETECTION = "numeric_detection";
private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats";
private final ElasticsearchConverter elasticsearchConverter;
@ -147,11 +150,24 @@ public class MappingBuilder {
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException {
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
if (!entity.getRequiredAnnotation(Mapping.class).enabled()) {
if (!mappingAnnotation.enabled()) {
builder.field(MAPPING_ENABLED, false);
return;
}
if (mappingAnnotation.dateDetection() != Mapping.Detection.DEFAULT) {
builder.field(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name()));
}
if (mappingAnnotation.numericDetection() != Mapping.Detection.DEFAULT) {
builder.field(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name()));
}
if (mappingAnnotation.dynamicDateFormats().length > 0) {
builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats());
}
}
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);

View File

@ -306,6 +306,17 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
indexOps.delete();
}
@Test // #638
@DisplayName("should write dynamic detection values")
void shouldWriteDynamicDetectionValues() {
IndexOperations indexOps = operations.indexOps(DynamicDetectionMapping.class);
indexOps.create();
indexOps.putMapping();
indexOps.delete();
}
// region entities
@Document(indexName = "ignore-above-index")
static class IgnoreAboveEntity {
@Nullable @Id private String id;
@ -1113,4 +1124,12 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
}
}
@Document(indexName = "dynamic-detection-mapping-true")
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE,
dynamicDateFormats = { "MM/dd/yyyy" })
private static class DynamicDetectionMapping {
@Id @Nullable private String id;
}
// endregion
}

View File

@ -774,6 +774,87 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
assertEquals(expected, mapping, true);
}
@Test // #638
@DisplayName("should not write dynamic detection mapping entries in default setting")
void shouldNotWriteDynamicDetectionMappingEntriesInDefaultSetting() throws JSONException {
String expected = "{\n" + //
" \"properties\": {\n" + //
" \"_class\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"index\": false,\n" + //
" \"doc_values\": false\n" + //
" }\n" + //
" }\n" + //
"}"; //
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingDefault.class);
assertEquals(expected, mapping, true);
}
@Test // #638
@DisplayName("should write dynamic detection mapping entries when set to false")
void shouldWriteDynamicDetectionMappingEntriesWhenSetToFalse() throws JSONException {
String expected = "{\n" + //
" \"date_detection\": false," + //
" \"numeric_detection\": false," + //
" \"properties\": {\n" + //
" \"_class\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"index\": false,\n" + //
" \"doc_values\": false\n" + //
" }\n" + //
" }\n" + //
"}"; //
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingFalse.class);
assertEquals(expected, mapping, true);
}
@Test // #638
@DisplayName("should write dynamic detection mapping entries when set to true")
void shouldWriteDynamicDetectionMappingEntriesWhenSetToTrue() throws JSONException {
String expected = "{\n" + //
" \"date_detection\": true," + //
" \"numeric_detection\": true," + //
" \"properties\": {\n" + //
" \"_class\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"index\": false,\n" + //
" \"doc_values\": false\n" + //
" }\n" + //
" }\n" + //
"}"; //
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingTrue.class);
assertEquals(expected, mapping, true);
}
@Test // #638
@DisplayName("should write dynamic date formats")
void shouldWriteDynamicDateFormats() throws JSONException {
String expected = "{\n" + //
" \"dynamic_date_formats\": [\"date1\",\"date2\"]," + //
" \"properties\": {\n" + //
" \"_class\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"index\": false,\n" + //
" \"doc_values\": false\n" + //
" }\n" + //
" }\n" + //
"}"; //
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDateFormatsMapping.class);
assertEquals(expected, mapping, true);
}
// region entities
@Document(indexName = "ignore-above-index")
static class IgnoreAboveEntity {
@ -1690,5 +1771,28 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
@Field(type = Text) @Nullable private String title;
@Field(type = Nested) @Nullable private List<Author> authors;
}
@Document(indexName = "dynamic-field-mapping-default")
private static class DynamicDetectionMappingDefault {
@Id @Nullable private String id;
}
@Document(indexName = "dynamic-dateformats-mapping")
@Mapping(dynamicDateFormats = {"date1", "date2"})
private static class DynamicDateFormatsMapping {
@Id @Nullable private String id;
}
@Document(indexName = "dynamic-detection-mapping-true")
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE)
private static class DynamicDetectionMappingTrue {
@Id @Nullable private String id;
}
@Document(indexName = "dynamic-detection-mapping-false")
@Mapping(dateDetection = Mapping.Detection.FALSE, numericDetection = Mapping.Detection.FALSE)
private static class DynamicDetectionMappingFalse {
@Id @Nullable private String id;
}
// endregion
}