Skip to content

Commit 38b1763

Browse files
authored
datatype detection support in mapping.
Original Pull Request #1810 Closes #638
1 parent df0d65e commit 38b1763

File tree

6 files changed

+178
-8
lines changed

6 files changed

+178
-8
lines changed

src/main/asciidoc/reference/elasticsearch-misc.adoc

+10
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,16 @@ class Entity {
4646
<.> `sortModes`, `sortOrders` and `sortMissingValues` are optional, but if they are set, the number of entries must match the number of `sortFields` elements
4747
====
4848

49+
[[elasticsearch.misc.mappings]]
50+
== Index Mapping
51+
52+
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:
53+
54+
* `mappingPath` a classpath resource in JSON format which is used as the mapping, no other mapping processing is done.
55+
* `enabled` when set to false, this flag is written to the mapping and no further processing is done.
56+
* `dateDetection` and `numericDetection` set the corresponding properties in the mapping when not set to `DEFAULT`.
57+
* `dynamicDateFormats` when this String array is not empty, it defines the date formats used for automatic date detection.
58+
4959
[[elasticsearch.misc.filter]]
5060
== Filter Builder
5161

src/main/java/org/springframework/data/elasticsearch/annotations/DynamicTemplates.java

+4-6
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,17 @@
99
import org.springframework.data.annotation.Persistent;
1010

1111
/**
12-
* Elasticsearch dynamic templates mapping.
13-
* This annotation is handy if you prefer apply dynamic templates on fields with annotation e.g. {@link Field}
14-
* with type = FieldType.Object etc. instead of static mapping on Document via {@link Mapping} annotation.
15-
* DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
12+
* Elasticsearch dynamic templates mapping. This annotation is handy if you prefer apply dynamic templates on fields
13+
* with annotation e.g. {@link Field} with type = FieldType.Object etc. instead of static mapping on Document via
14+
* {@link Mapping} annotation. DynamicTemplates annotation is omitted if {@link Mapping} annotation is used.
1615
*
1716
* @author Petr Kukral
1817
*/
1918
@Persistent
2019
@Inherited
2120
@Retention(RetentionPolicy.RUNTIME)
22-
@Target({ElementType.TYPE})
21+
@Target({ ElementType.TYPE })
2322
public @interface DynamicTemplates {
2423

2524
String mappingPath() default "";
26-
2725
}

src/main/java/org/springframework/data/elasticsearch/annotations/Mapping.java

+24-1
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,31 @@
3838

3939
/**
4040
* whether mappings are enabled
41-
*
41+
*
4242
* @since 4.2
4343
*/
4444
boolean enabled() default true;
45+
/**
46+
* whether date_detection is enabled
47+
*
48+
* @since 4.3
49+
*/
50+
Detection dateDetection() default Detection.DEFAULT;
51+
52+
/**
53+
* whether numeric_detection is enabled
54+
*
55+
* @since 4.3
56+
*/
57+
Detection numericDetection() default Detection.DEFAULT;
58+
59+
/**
60+
* custom dynamic date formats
61+
* @since 4.3
62+
*/
63+
String[] dynamicDateFormats() default {};
64+
65+
enum Detection {
66+
DEFAULT, TRUE, FALSE;
67+
}
4568
}

src/main/java/org/springframework/data/elasticsearch/core/index/MappingBuilder.java

+17-1
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,9 @@ public class MappingBuilder {
9292
private static final String JOIN_TYPE_RELATIONS = "relations";
9393

9494
private static final String MAPPING_ENABLED = "enabled";
95+
private static final String DATE_DETECTION = "date_detection";
96+
private static final String NUMERIC_DETECTION = "numeric_detection";
97+
private static final String DYNAMIC_DATE_FORMATS = "dynamic_date_formats";
9598

9699
private final ElasticsearchConverter elasticsearchConverter;
97100

@@ -147,11 +150,24 @@ private void mapEntity(XContentBuilder builder, @Nullable ElasticsearchPersisten
147150
@Nullable Field parentFieldAnnotation, @Nullable DynamicMapping dynamicMapping) throws IOException {
148151

149152
if (entity != null && entity.isAnnotationPresent(Mapping.class)) {
153+
Mapping mappingAnnotation = entity.getRequiredAnnotation(Mapping.class);
150154

151-
if (!entity.getRequiredAnnotation(Mapping.class).enabled()) {
155+
if (!mappingAnnotation.enabled()) {
152156
builder.field(MAPPING_ENABLED, false);
153157
return;
154158
}
159+
160+
if (mappingAnnotation.dateDetection() != Mapping.Detection.DEFAULT) {
161+
builder.field(DATE_DETECTION, Boolean.parseBoolean(mappingAnnotation.dateDetection().name()));
162+
}
163+
164+
if (mappingAnnotation.numericDetection() != Mapping.Detection.DEFAULT) {
165+
builder.field(NUMERIC_DETECTION, Boolean.parseBoolean(mappingAnnotation.numericDetection().name()));
166+
}
167+
168+
if (mappingAnnotation.dynamicDateFormats().length > 0) {
169+
builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats());
170+
}
155171
}
156172

157173
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderIntegrationTests.java

+19
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,17 @@ void shouldWriteDynamicMappingEntries() {
306306
indexOps.delete();
307307
}
308308

309+
@Test // #638
310+
@DisplayName("should write dynamic detection values")
311+
void shouldWriteDynamicDetectionValues() {
312+
313+
IndexOperations indexOps = operations.indexOps(DynamicDetectionMapping.class);
314+
indexOps.create();
315+
indexOps.putMapping();
316+
indexOps.delete();
317+
}
318+
319+
// region entities
309320
@Document(indexName = "ignore-above-index")
310321
static class IgnoreAboveEntity {
311322
@Nullable @Id private String id;
@@ -1113,4 +1124,12 @@ public void setAuthor(Author author) {
11131124
}
11141125
}
11151126

1127+
@Document(indexName = "dynamic-detection-mapping-true")
1128+
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE,
1129+
dynamicDateFormats = { "MM/dd/yyyy" })
1130+
private static class DynamicDetectionMapping {
1131+
@Id @Nullable private String id;
1132+
}
1133+
// endregion
1134+
11161135
}

src/test/java/org/springframework/data/elasticsearch/core/index/MappingBuilderUnitTests.java

+104
Original file line numberDiff line numberDiff line change
@@ -774,6 +774,87 @@ void shouldNotWriteTypeHintsWhenContextIsConfiguredToDoSoButEntityShouldNot() th
774774
assertEquals(expected, mapping, true);
775775
}
776776

777+
@Test // #638
778+
@DisplayName("should not write dynamic detection mapping entries in default setting")
779+
void shouldNotWriteDynamicDetectionMappingEntriesInDefaultSetting() throws JSONException {
780+
781+
String expected = "{\n" + //
782+
" \"properties\": {\n" + //
783+
" \"_class\": {\n" + //
784+
" \"type\": \"keyword\",\n" + //
785+
" \"index\": false,\n" + //
786+
" \"doc_values\": false\n" + //
787+
" }\n" + //
788+
" }\n" + //
789+
"}"; //
790+
791+
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingDefault.class);
792+
793+
assertEquals(expected, mapping, true);
794+
}
795+
796+
@Test // #638
797+
@DisplayName("should write dynamic detection mapping entries when set to false")
798+
void shouldWriteDynamicDetectionMappingEntriesWhenSetToFalse() throws JSONException {
799+
800+
String expected = "{\n" + //
801+
" \"date_detection\": false," + //
802+
" \"numeric_detection\": false," + //
803+
" \"properties\": {\n" + //
804+
" \"_class\": {\n" + //
805+
" \"type\": \"keyword\",\n" + //
806+
" \"index\": false,\n" + //
807+
" \"doc_values\": false\n" + //
808+
" }\n" + //
809+
" }\n" + //
810+
"}"; //
811+
812+
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingFalse.class);
813+
814+
assertEquals(expected, mapping, true);
815+
}
816+
817+
@Test // #638
818+
@DisplayName("should write dynamic detection mapping entries when set to true")
819+
void shouldWriteDynamicDetectionMappingEntriesWhenSetToTrue() throws JSONException {
820+
821+
String expected = "{\n" + //
822+
" \"date_detection\": true," + //
823+
" \"numeric_detection\": true," + //
824+
" \"properties\": {\n" + //
825+
" \"_class\": {\n" + //
826+
" \"type\": \"keyword\",\n" + //
827+
" \"index\": false,\n" + //
828+
" \"doc_values\": false\n" + //
829+
" }\n" + //
830+
" }\n" + //
831+
"}"; //
832+
833+
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDetectionMappingTrue.class);
834+
835+
assertEquals(expected, mapping, true);
836+
}
837+
838+
@Test // #638
839+
@DisplayName("should write dynamic date formats")
840+
void shouldWriteDynamicDateFormats() throws JSONException {
841+
842+
String expected = "{\n" + //
843+
" \"dynamic_date_formats\": [\"date1\",\"date2\"]," + //
844+
" \"properties\": {\n" + //
845+
" \"_class\": {\n" + //
846+
" \"type\": \"keyword\",\n" + //
847+
" \"index\": false,\n" + //
848+
" \"doc_values\": false\n" + //
849+
" }\n" + //
850+
" }\n" + //
851+
"}"; //
852+
853+
String mapping = getMappingBuilder().buildPropertyMapping(DynamicDateFormatsMapping.class);
854+
855+
assertEquals(expected, mapping, true);
856+
}
857+
777858
// region entities
778859
@Document(indexName = "ignore-above-index")
779860
static class IgnoreAboveEntity {
@@ -1690,5 +1771,28 @@ private static class MagazineWithTypeHints {
16901771
@Field(type = Text) @Nullable private String title;
16911772
@Field(type = Nested) @Nullable private List<Author> authors;
16921773
}
1774+
1775+
@Document(indexName = "dynamic-field-mapping-default")
1776+
private static class DynamicDetectionMappingDefault {
1777+
@Id @Nullable private String id;
1778+
}
1779+
1780+
@Document(indexName = "dynamic-dateformats-mapping")
1781+
@Mapping(dynamicDateFormats = {"date1", "date2"})
1782+
private static class DynamicDateFormatsMapping {
1783+
@Id @Nullable private String id;
1784+
}
1785+
1786+
@Document(indexName = "dynamic-detection-mapping-true")
1787+
@Mapping(dateDetection = Mapping.Detection.TRUE, numericDetection = Mapping.Detection.TRUE)
1788+
private static class DynamicDetectionMappingTrue {
1789+
@Id @Nullable private String id;
1790+
}
1791+
1792+
@Document(indexName = "dynamic-detection-mapping-false")
1793+
@Mapping(dateDetection = Mapping.Detection.FALSE, numericDetection = Mapping.Detection.FALSE)
1794+
private static class DynamicDetectionMappingFalse {
1795+
@Id @Nullable private String id;
1796+
}
16931797
// endregion
16941798
}

0 commit comments

Comments
 (0)