Add runtime fields to index mapping.

Original Pull Request: #1820 
Closes: #1816
This commit is contained in:
Peter-Josef Meisch 2021-05-19 21:38:48 +02:00 committed by GitHub
parent 25b323c00d
commit 0836411d45
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 108 additions and 4 deletions

View File

@ -51,10 +51,24 @@ class Entity {
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.
* `mappingPath` a classpath resource in JSON format; if this is not empty it 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.
* `runtimeFieldsPath` a classpath resource in JSON format containing the definition of runtime fields which is written to the index mappings, for example:
====
[source,json]
----
{
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}
----
====
[[elasticsearch.misc.filter]]
== Filter Builder

View File

@ -27,6 +27,7 @@ import org.springframework.data.annotation.Persistent;
* Elasticsearch Mapping
*
* @author Mohsin Husen
* @author Peter-Josef Meisch
*/
@Persistent
@Inherited
@ -42,6 +43,7 @@ public @interface Mapping {
* @since 4.2
*/
boolean enabled() default true;
/**
* whether date_detection is enabled
*
@ -58,10 +60,20 @@ public @interface Mapping {
/**
* custom dynamic date formats
*
* @since 4.3
*/
String[] dynamicDateFormats() default {};
/**
* classpath to a JSON file containing the values for a runtime mapping definition. The file must contain the JSON
* object that is written as the value of the runtime property. {@see <a href=
* "https://www.elastic.co/guide/en/elasticsearch/reference/7.12/runtime-mapping-fields.html">elasticsearch doc</a>}
*
* @since 4.3
*/
String runtimeFieldsPath() default "";
enum Detection {
DEFAULT, TRUE, FALSE;
}

View File

@ -219,8 +219,6 @@ abstract class AbstractDefaultIndexOperations implements IndexOperations {
if (hasText(mappings)) {
return Document.parse(mappings);
}
} else {
LOGGER.info("mappingPath in @Mapping has to be defined. Building mappings using @Field");
}
}

View File

@ -95,6 +95,7 @@ public class MappingBuilder {
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 static final String RUNTIME = "runtime";
private final ElasticsearchConverter elasticsearchConverter;
@ -168,6 +169,10 @@ public class MappingBuilder {
if (mappingAnnotation.dynamicDateFormats().length > 0) {
builder.field(DYNAMIC_DATE_FORMATS, mappingAnnotation.dynamicDateFormats());
}
if (StringUtils.hasText(mappingAnnotation.runtimeFieldsPath())) {
addRuntimeFields(builder, mappingAnnotation.runtimeFieldsPath());
}
}
boolean writeNestedProperties = !isRootObject && (isAnyPropertyAnnotatedWithField(entity) || nestedOrObjectField);
@ -222,6 +227,15 @@ public class MappingBuilder {
}
private void addRuntimeFields(XContentBuilder builder, String runtimeFieldsPath) throws IOException {
ClassPathResource runtimeFields = new ClassPathResource(runtimeFieldsPath);
if (runtimeFields.exists()) {
builder.rawField(RUNTIME, runtimeFields.getInputStream(), XContentType.JSON);
}
}
private void buildPropertyMapping(XContentBuilder builder, boolean isRootObject,
ElasticsearchPersistentProperty property) throws IOException {

View File

@ -25,6 +25,7 @@ import static org.springframework.data.elasticsearch.utils.IndexBuilder.*;
import java.lang.Integer;
import java.lang.Object;
import java.math.BigDecimal;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
@ -316,6 +317,16 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
indexOps.delete();
}
@Test // #1816
@DisplayName("should write runtime fields")
void shouldWriteRuntimeFields() {
IndexOperations indexOps = operations.indexOps(RuntimeFieldEntity.class);
indexOps.create();
indexOps.putMapping();
indexOps.delete();
}
// region entities
@Document(indexName = "ignore-above-index")
static class IgnoreAboveEntity {
@ -1130,6 +1141,14 @@ public class MappingBuilderIntegrationTests extends MappingContextBaseTests {
private static class DynamicDetectionMapping {
@Id @Nullable private String id;
}
@Document(indexName = "runtime-fields")
@Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json")
private static class RuntimeFieldEntity {
@Id @Nullable private String id;
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp;
}
// endregion
}

View File

@ -26,6 +26,7 @@ import java.lang.Double;
import java.lang.Integer;
import java.lang.Object;
import java.math.BigDecimal;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Collection;
@ -855,7 +856,38 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
assertEquals(expected, mapping, true);
}
@Test // #1816
@DisplayName("should write runtime fields")
void shouldWriteRuntimeFields() throws JSONException {
String expected = "{\n" + //
" \"runtime\": {\n" + //
" \"day_of_week\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"script\": {\n" + //
" \"source\": \"emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))\"\n" + //
" }\n" + //
" }\n" + //
" },\n" + //
" \"properties\": {\n" + //
" \"_class\": {\n" + //
" \"type\": \"keyword\",\n" + //
" \"index\": false,\n" + //
" \"doc_values\": false\n" + //
" },\n" + //
" \"@timestamp\": {\n" + //
" \"type\": \"date\",\n" + //
" \"format\": \"epoch_millis\"\n" + //
" }\n" + //
" }\n" + //
"}\n"; //
String mapping = getMappingBuilder().buildPropertyMapping(RuntimeFieldEntity.class);
assertEquals(expected, mapping, true);
}
// region entities
@Document(indexName = "ignore-above-index")
static class IgnoreAboveEntity {
@Nullable @Id private String id;
@ -1778,7 +1810,7 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
}
@Document(indexName = "dynamic-dateformats-mapping")
@Mapping(dynamicDateFormats = {"date1", "date2"})
@Mapping(dynamicDateFormats = { "date1", "date2" })
private static class DynamicDateFormatsMapping {
@Id @Nullable private String id;
}
@ -1794,5 +1826,12 @@ public class MappingBuilderUnitTests extends MappingContextBaseTests {
private static class DynamicDetectionMappingFalse {
@Id @Nullable private String id;
}
@Document(indexName = "runtime-fields")
@Mapping(runtimeFieldsPath = "/mappings/runtime-fields.json")
private static class RuntimeFieldEntity {
@Id @Nullable private String id;
@Field(type = Date, format = DateFormat.epoch_millis, name = "@timestamp") @Nullable private Instant timestamp;
}
// endregion
}

View File

@ -0,0 +1,8 @@
{
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))"
}
}
}