Skip to content

Commit 8234603

Browse files
authored
Introduce runtime section in mappings (#62906)
The runtime section is at the same level as the existing properties section. Its purpose is to hold runtime fields only. With the introduction of the runtime section, a runtime field can be defined by specifying its type (previously called runtime_type) and script. ``` PUT /my-index/_mappings { "runtime" : { "day_of_week" : { "type" : "keyword", "script" : { "source" : "emit(doc['timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ROOT))" } } }, "properties" : { "timestamp" : { "type" : "date" } } } ``` Fields defined in the runtime section can be updated at any time as they are not present in the lucene index. They get replaced entirely when they get updated. Thanks to the introduction of the runtime section, runtime fields override existing mapped fields defined with the same name, similarly to runtime fields defined in the search request. Relates to #59332
1 parent 919a729 commit 8234603

File tree

66 files changed

+1855
-1493
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1855
-1493
lines changed

server/src/main/java/org/elasticsearch/action/admin/indices/mapping/get/TransportGetFieldMappingsIndexAction.java

+2
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,8 @@ public Boolean paramAsBoolean(String key, Boolean defaultValue) {
173173
private static Map<String, FieldMappingMetadata> findFieldMappingsByType(Predicate<String> fieldPredicate,
174174
DocumentMapper documentMapper,
175175
GetFieldMappingsIndexRequest request) {
176+
//TODO the logic here needs to be reworked to also include runtime fields. Though matching is against mappers rather
177+
// than field types, and runtime fields are mixed with ordinary fields in FieldTypeLookup
176178
Map<String, FieldMappingMetadata> fieldMappings = new HashMap<>();
177179
final MappingLookup mappingLookup = documentMapper.mappers();
178180
for (String field : request.fields()) {

server/src/main/java/org/elasticsearch/index/mapper/FieldMapper.java

+11-11
Original file line numberDiff line numberDiff line change
@@ -279,15 +279,7 @@ public final FieldMapper merge(Mapper mergeWith) {
279279
Conflicts conflicts = new Conflicts(name());
280280
builder.merge((FieldMapper) mergeWith, conflicts);
281281
conflicts.check();
282-
return builder.build(parentPath(name()));
283-
}
284-
285-
private static ContentPath parentPath(String name) {
286-
int endPos = name.lastIndexOf(".");
287-
if (endPos == -1) {
288-
return new ContentPath(0);
289-
}
290-
return new ContentPath(name.substring(0, endPos));
282+
return builder.build(Builder.parentPath(name()));
291283
}
292284

293285
protected void checkIncomingMergeType(FieldMapper mergeWith) {
@@ -483,7 +475,7 @@ public List<String> copyToFields() {
483475
/**
484476
* Serializes a parameter
485477
*/
486-
protected interface Serializer<T> {
478+
public interface Serializer<T> {
487479
void serialize(XContentBuilder builder, String name, T value) throws IOException;
488480
}
489481

@@ -936,7 +928,7 @@ protected String buildFullName(ContentPath contentPath) {
936928
/**
937929
* Writes the current builder parameter values as XContent
938930
*/
939-
protected final void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException {
931+
public final void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException {
940932
for (Parameter<?> parameter : getParameters()) {
941933
parameter.toXContent(builder, includeDefaults);
942934
}
@@ -1010,6 +1002,14 @@ public final void parse(String name, ParserContext parserContext, Map<String, Ob
10101002
validate();
10111003
}
10121004

1005+
protected static ContentPath parentPath(String name) {
1006+
int endPos = name.lastIndexOf(".");
1007+
if (endPos == -1) {
1008+
return new ContentPath(0);
1009+
}
1010+
return new ContentPath(name.substring(0, endPos));
1011+
}
1012+
10131013
// These parameters were previously *always* parsed by TypeParsers#parseField(), even if they
10141014
// made no sense; if we've got here, that means that they're not declared on a current mapper,
10151015
// and so we emit a deprecation warning rather than failing a previously working mapping.

server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
* An immutable container for looking up {@link MappedFieldType}s by their name.
3434
*/
3535
final class FieldTypeLookup {
36-
3736
private final Map<String, MappedFieldType> fullNameToFieldType = new HashMap<>();
3837

3938
/**
@@ -47,7 +46,8 @@ final class FieldTypeLookup {
4746
private final DynamicKeyFieldTypeLookup dynamicKeyLookup;
4847

4948
FieldTypeLookup(Collection<FieldMapper> fieldMappers,
50-
Collection<FieldAliasMapper> fieldAliasMappers) {
49+
Collection<FieldAliasMapper> fieldAliasMappers,
50+
Collection<RuntimeFieldType> runtimeFieldTypes) {
5151
Map<String, DynamicKeyFieldMapper> dynamicKeyMappers = new HashMap<>();
5252

5353
for (FieldMapper fieldMapper : fieldMappers) {
@@ -77,6 +77,11 @@ final class FieldTypeLookup {
7777
fullNameToFieldType.put(aliasName, fullNameToFieldType.get(path));
7878
}
7979

80+
for (RuntimeFieldType runtimeFieldType : runtimeFieldTypes) {
81+
//this will override concrete fields with runtime fields that have the same name
82+
fullNameToFieldType.put(runtimeFieldType.name(), runtimeFieldType);
83+
}
84+
8085
this.dynamicKeyLookup = new DynamicKeyFieldTypeLookup(dynamicKeyMappers, aliasToConcreteName);
8186
}
8287

server/src/main/java/org/elasticsearch/index/mapper/Mapper.java

+8-2
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class ParserContext {
5959

6060
private final Function<String, SimilarityProvider> similarityLookupService;
6161
private final Function<String, TypeParser> typeParsers;
62+
private final Function<String, RuntimeFieldType.Parser> runtimeTypeParsers;
6263
private final Version indexVersionCreated;
6364
private final Supplier<QueryShardContext> queryShardContextSupplier;
6465
private final DateFormatter dateFormatter;
@@ -69,6 +70,7 @@ class ParserContext {
6970

7071
public ParserContext(Function<String, SimilarityProvider> similarityLookupService,
7172
Function<String, TypeParser> typeParsers,
73+
Function<String, RuntimeFieldType.Parser> runtimeTypeParsers,
7274
Version indexVersionCreated,
7375
Supplier<QueryShardContext> queryShardContextSupplier,
7476
DateFormatter dateFormatter,
@@ -78,6 +80,7 @@ public ParserContext(Function<String, SimilarityProvider> similarityLookupServic
7880
BooleanSupplier idFieldDataEnabled) {
7981
this.similarityLookupService = similarityLookupService;
8082
this.typeParsers = typeParsers;
83+
this.runtimeTypeParsers = runtimeTypeParsers;
8184
this.indexVersionCreated = indexVersionCreated;
8285
this.queryShardContextSupplier = queryShardContextSupplier;
8386
this.dateFormatter = dateFormatter;
@@ -132,6 +135,8 @@ public DateFormatter getDateFormatter() {
132135

133136
protected Function<String, TypeParser> typeParsers() { return typeParsers; }
134137

138+
protected Function<String, RuntimeFieldType.Parser> runtimeTypeParsers() { return runtimeTypeParsers; }
139+
135140
protected Function<String, SimilarityProvider> similarityLookupService() { return similarityLookupService; }
136141

137142
/**
@@ -147,8 +152,9 @@ public ParserContext createMultiFieldContext(ParserContext in) {
147152

148153
static class MultiFieldParserContext extends ParserContext {
149154
MultiFieldParserContext(ParserContext in) {
150-
super(in.similarityLookupService, in.typeParsers, in.indexVersionCreated, in.queryShardContextSupplier,
151-
in.dateFormatter, in.scriptService, in.indexAnalyzers, in.indexSettings, in.idFieldDataEnabled);
155+
super(in.similarityLookupService, in.typeParsers, in.runtimeTypeParsers, in.indexVersionCreated,
156+
in.queryShardContextSupplier, in.dateFormatter, in.scriptService, in.indexAnalyzers, in.indexSettings,
157+
in.idFieldDataEnabled);
152158
}
153159

154160
@Override

server/src/main/java/org/elasticsearch/index/mapper/MapperService.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -149,8 +149,8 @@ public MapperService(IndexSettings indexSettings, IndexAnalyzers indexAnalyzers,
149149
this.mapperRegistry = mapperRegistry;
150150
Function<DateFormatter, Mapper.TypeParser.ParserContext> parserContextFunction =
151151
dateFormatter -> new Mapper.TypeParser.ParserContext(similarityService::getSimilarity, mapperRegistry.getMapperParsers()::get,
152-
indexVersionCreated, queryShardContextSupplier, dateFormatter, scriptService, indexAnalyzers, indexSettings,
153-
idFieldDataEnabled);
152+
mapperRegistry.getRuntimeFieldTypeParsers()::get, indexVersionCreated, queryShardContextSupplier, dateFormatter,
153+
scriptService, indexAnalyzers, indexSettings, idFieldDataEnabled);
154154
this.documentParser = new DocumentParser(xContentRegistry, parserContextFunction);
155155
Map<String, MetadataFieldMapper.TypeParser> metadataMapperParsers =
156156
mapperRegistry.getMetadataMapperParsers(indexSettings.getIndexVersionCreated());

server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import java.util.stream.Stream;
3333

3434
public final class MappingLookup {
35-
3635
/** Full field name to mapper */
3736
private final Map<String, Mapper> fieldMappers;
3837
private final Map<String, ObjectMapper> objectMappers;
@@ -50,24 +49,24 @@ public static MappingLookup fromMapping(Mapping mapping) {
5049
newFieldMappers.add(metadataMapper);
5150
}
5251
}
53-
collect(mapping.root, newObjectMappers, newFieldMappers, newFieldAliasMappers);
54-
return new MappingLookup(newFieldMappers, newObjectMappers, newFieldAliasMappers, mapping.metadataMappers.length);
52+
for (Mapper child : mapping.root) {
53+
collect(child, newObjectMappers, newFieldMappers, newFieldAliasMappers);
54+
}
55+
return new MappingLookup(newFieldMappers, newObjectMappers, newFieldAliasMappers,
56+
mapping.root.runtimeFieldTypes(), mapping.metadataMappers.length);
5557
}
5658

5759
private static void collect(Mapper mapper, Collection<ObjectMapper> objectMappers,
5860
Collection<FieldMapper> fieldMappers,
5961
Collection<FieldAliasMapper> fieldAliasMappers) {
60-
if (mapper instanceof RootObjectMapper) {
61-
// root mapper isn't really an object mapper
62-
} else if (mapper instanceof ObjectMapper) {
62+
if (mapper instanceof ObjectMapper) {
6363
objectMappers.add((ObjectMapper)mapper);
6464
} else if (mapper instanceof FieldMapper) {
6565
fieldMappers.add((FieldMapper)mapper);
6666
} else if (mapper instanceof FieldAliasMapper) {
6767
fieldAliasMappers.add((FieldAliasMapper) mapper);
6868
} else {
69-
throw new IllegalStateException("Unrecognized mapper type [" +
70-
mapper.getClass().getSimpleName() + "].");
69+
throw new IllegalStateException("Unrecognized mapper type [" + mapper.getClass().getSimpleName() + "].");
7170
}
7271

7372
for (Mapper child : mapper) {
@@ -78,6 +77,7 @@ private static void collect(Mapper mapper, Collection<ObjectMapper> objectMapper
7877
public MappingLookup(Collection<FieldMapper> mappers,
7978
Collection<ObjectMapper> objectMappers,
8079
Collection<FieldAliasMapper> aliasMappers,
80+
Collection<RuntimeFieldType> runtimeFieldTypes,
8181
int metadataFieldCount) {
8282
Map<String, Mapper> fieldMappers = new HashMap<>();
8383
Map<String, Analyzer> indexAnalyzers = new HashMap<>();
@@ -114,7 +114,7 @@ public MappingLookup(Collection<FieldMapper> mappers,
114114
}
115115
}
116116

117-
this.fieldTypeLookup = new FieldTypeLookup(mappers, aliasMappers);
117+
this.fieldTypeLookup = new FieldTypeLookup(mappers, aliasMappers, runtimeFieldTypes);
118118

119119
this.fieldMappers = Collections.unmodifiableMap(fieldMappers);
120120
this.indexAnalyzer = new FieldNameAnalyzer(indexAnalyzers);

server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java

+44-10
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
package org.elasticsearch.index.mapper;
2121

22+
import org.elasticsearch.ElasticsearchParseException;
2223
import org.elasticsearch.Version;
2324
import org.elasticsearch.common.Explicit;
2425
import org.elasticsearch.common.Strings;
@@ -34,11 +35,14 @@
3435
import java.util.Arrays;
3536
import java.util.Collection;
3637
import java.util.Collections;
38+
import java.util.Comparator;
39+
import java.util.HashMap;
3740
import java.util.Iterator;
3841
import java.util.LinkedHashMap;
3942
import java.util.List;
4043
import java.util.Locale;
4144
import java.util.Map;
45+
import java.util.stream.Collectors;
4246

4347
import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue;
4448
import static org.elasticsearch.index.mapper.TypeParsers.parseDateTimeFormatter;
@@ -62,6 +66,7 @@ public static class Builder extends ObjectMapper.Builder {
6266
protected Explicit<DateFormatter[]> dynamicDateTimeFormatters = new Explicit<>(Defaults.DYNAMIC_DATE_TIME_FORMATTERS, false);
6367
protected Explicit<Boolean> dateDetection = new Explicit<>(Defaults.DATE_DETECTION, false);
6468
protected Explicit<Boolean> numericDetection = new Explicit<>(Defaults.NUMERIC_DETECTION, false);
69+
protected final Map<String, RuntimeFieldType> runtimeFieldTypes = new HashMap<>();
6570

6671
public Builder(String name, Version indexCreatedVersion) {
6772
super(name, indexCreatedVersion);
@@ -83,6 +88,11 @@ public RootObjectMapper.Builder add(Mapper.Builder builder) {
8388
return this;
8489
}
8590

91+
public RootObjectMapper.Builder addRuntime(RuntimeFieldType runtimeFieldType) {
92+
this.runtimeFieldTypes.put(runtimeFieldType.name(), runtimeFieldType);
93+
return this;
94+
}
95+
8696
@Override
8797
public RootObjectMapper build(ContentPath contentPath) {
8898
return (RootObjectMapper) super.build(contentPath);
@@ -92,7 +102,7 @@ public RootObjectMapper build(ContentPath contentPath) {
92102
protected ObjectMapper createMapper(String name, String fullPath, Explicit<Boolean> enabled, Nested nested, Dynamic dynamic,
93103
Map<String, Mapper> mappers, Version indexCreatedVersion) {
94104
assert !nested.isNested();
95-
return new RootObjectMapper(name, enabled, dynamic, mappers,
105+
return new RootObjectMapper(name, enabled, dynamic, mappers, runtimeFieldTypes,
96106
dynamicDateTimeFormatters,
97107
dynamicTemplates,
98108
dateDetection, numericDetection, indexCreatedVersion);
@@ -127,7 +137,7 @@ private static void fixRedundantIncludes(ObjectMapper objectMapper, boolean pare
127137
}
128138
}
129139

130-
public static class TypeParser extends ObjectMapper.TypeParser {
140+
static final class TypeParser extends ObjectMapper.TypeParser {
131141

132142
@Override
133143
public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext parserContext) throws MapperParsingException {
@@ -145,8 +155,8 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
145155
return builder;
146156
}
147157

148-
protected boolean processField(RootObjectMapper.Builder builder, String fieldName, Object fieldNode,
149-
ParserContext parserContext) {
158+
@SuppressWarnings("unchecked")
159+
private boolean processField(RootObjectMapper.Builder builder, String fieldName, Object fieldNode, ParserContext parserContext) {
150160
if (fieldName.equals("date_formats") || fieldName.equals("dynamic_date_formats")) {
151161
if (fieldNode instanceof List) {
152162
List<DateFormatter> formatters = new ArrayList<>();
@@ -200,6 +210,13 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
200210
} else if (fieldName.equals("numeric_detection")) {
201211
builder.numericDetection = new Explicit<>(nodeBooleanValue(fieldNode, "numeric_detection"), true);
202212
return true;
213+
} else if (fieldName.equals("runtime")) {
214+
if (fieldNode instanceof Map) {
215+
RuntimeFieldType.parseRuntimeFields((Map<String, Object>) fieldNode, parserContext, builder::addRuntime);
216+
return true;
217+
} else {
218+
throw new ElasticsearchParseException("runtime must be a map type");
219+
}
203220
}
204221
return false;
205222
}
@@ -209,11 +226,14 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
209226
private Explicit<Boolean> dateDetection;
210227
private Explicit<Boolean> numericDetection;
211228
private Explicit<DynamicTemplate[]> dynamicTemplates;
229+
private final Map<String, RuntimeFieldType> runtimeFieldTypes;
212230

213231
RootObjectMapper(String name, Explicit<Boolean> enabled, Dynamic dynamic, Map<String, Mapper> mappers,
232+
Map<String, RuntimeFieldType> runtimeFieldTypes,
214233
Explicit<DateFormatter[]> dynamicDateTimeFormatters, Explicit<DynamicTemplate[]> dynamicTemplates,
215234
Explicit<Boolean> dateDetection, Explicit<Boolean> numericDetection, Version indexCreatedVersion) {
216235
super(name, name, enabled, Nested.NO, dynamic, mappers, indexCreatedVersion);
236+
this.runtimeFieldTypes = runtimeFieldTypes;
217237
this.dynamicTemplates = dynamicTemplates;
218238
this.dynamicDateTimeFormatters = dynamicDateTimeFormatters;
219239
this.dateDetection = dateDetection;
@@ -233,23 +253,26 @@ public ObjectMapper mappingUpdate(Mapper mapper) {
233253
return update;
234254
}
235255

236-
public boolean dateDetection() {
256+
boolean dateDetection() {
237257
return this.dateDetection.value();
238258
}
239259

240-
public boolean numericDetection() {
260+
boolean numericDetection() {
241261
return this.numericDetection.value();
242262
}
243263

244-
public DateFormatter[] dynamicDateTimeFormatters() {
264+
DateFormatter[] dynamicDateTimeFormatters() {
245265
return dynamicDateTimeFormatters.value();
246266
}
247267

248-
public DynamicTemplate[] dynamicTemplates() {
268+
DynamicTemplate[] dynamicTemplates() {
249269
return dynamicTemplates.value();
250270
}
251271

252-
@SuppressWarnings("rawtypes")
272+
Collection<RuntimeFieldType> runtimeFieldTypes() {
273+
return runtimeFieldTypes.values();
274+
}
275+
253276
public Mapper.Builder findTemplateBuilder(ParseContext context, String name, XContentFieldType matchType) {
254277
return findTemplateBuilder(context, name, matchType, null);
255278
}
@@ -265,7 +288,6 @@ public Mapper.Builder findTemplateBuilder(ParseContext context, String name, Dat
265288
* @param dateFormat a dateformatter to use if the type is a date, null if not a date or is using the default format
266289
* @return a mapper builder, or null if there is no template for such a field
267290
*/
268-
@SuppressWarnings("rawtypes")
269291
private Mapper.Builder findTemplateBuilder(ParseContext context, String name, XContentFieldType matchType, DateFormatter dateFormat) {
270292
DynamicTemplate dynamicTemplate = findTemplate(context.path(), name, matchType);
271293
if (dynamicTemplate == null) {
@@ -328,6 +350,8 @@ protected void doMerge(ObjectMapper mergeWith, MergeReason reason) {
328350
this.dynamicTemplates = mergeWithObject.dynamicTemplates;
329351
}
330352
}
353+
354+
this.runtimeFieldTypes.putAll(mergeWithObject.runtimeFieldTypes);
331355
}
332356

333357
@Override
@@ -358,6 +382,16 @@ protected void doXContent(XContentBuilder builder, ToXContent.Params params) thr
358382
if (numericDetection.explicit() || includeDefaults) {
359383
builder.field("numeric_detection", numericDetection.value());
360384
}
385+
386+
if (runtimeFieldTypes.size() > 0) {
387+
builder.startObject("runtime");
388+
List<RuntimeFieldType> sortedRuntimeFieldTypes = runtimeFieldTypes.values().stream().sorted(
389+
Comparator.comparing(RuntimeFieldType::name)).collect(Collectors.toList());
390+
for (RuntimeFieldType fieldType : sortedRuntimeFieldTypes) {
391+
fieldType.toXContent(builder, params);
392+
}
393+
builder.endObject();
394+
}
361395
}
362396

363397
private static void validateDynamicTemplate(Mapper.TypeParser.ParserContext parserContext,

0 commit comments

Comments
 (0)