Skip to content

Commit 3101293

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 db15c4d commit 3101293

File tree

66 files changed

+1845
-1508
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

+1845
-1508
lines changed

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,8 @@ private static Map<String, FieldMappingMetadata> findFieldMappings(Predicate<Str
155155
if (documentMapper == null) {
156156
return Collections.emptyMap();
157157
}
158+
//TODO the logic here needs to be reworked to also include runtime fields. Though matching is against mappers rather
159+
// than field types, and runtime fields are mixed with ordinary fields in FieldTypeLookup
158160
Map<String, FieldMappingMetadata> fieldMappings = new HashMap<>();
159161
final MappingLookup mappingLookup = documentMapper.mappers();
160162
for (String field : request.fields()) {

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

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

292284
protected void checkIncomingMergeType(FieldMapper mergeWith) {
@@ -482,7 +474,7 @@ public List<String> copyToFields() {
482474
/**
483475
* Serializes a parameter
484476
*/
485-
protected interface Serializer<T> {
477+
public interface Serializer<T> {
486478
void serialize(XContentBuilder builder, String name, T value) throws IOException;
487479
}
488480

@@ -931,7 +923,7 @@ protected String buildFullName(ContentPath contentPath) {
931923
/**
932924
* Writes the current builder parameter values as XContent
933925
*/
934-
protected final void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException {
926+
public final void toXContent(XContentBuilder builder, boolean includeDefaults) throws IOException {
935927
for (Parameter<?> parameter : getParameters()) {
936928
parameter.toXContent(builder, includeDefaults);
937929
}
@@ -1011,6 +1003,14 @@ public final void parse(String name, ParserContext parserContext, Map<String, Ob
10111003
validate();
10121004
}
10131005

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

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

Lines changed: 7 additions & 2 deletions
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

Lines changed: 8 additions & 2 deletions
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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ public MapperService(IndexSettings indexSettings, IndexAnalyzers indexAnalyzers,
125125
this.mapperRegistry = mapperRegistry;
126126
Function<DateFormatter, Mapper.TypeParser.ParserContext> parserContextFunction =
127127
dateFormatter -> new Mapper.TypeParser.ParserContext(similarityService::getSimilarity, mapperRegistry.getMapperParsers()::get,
128-
indexVersionCreated, queryShardContextSupplier, dateFormatter, scriptService, indexAnalyzers, indexSettings,
129-
idFieldDataEnabled);
128+
mapperRegistry.getRuntimeFieldTypeParsers()::get, indexVersionCreated, queryShardContextSupplier, dateFormatter,
129+
scriptService, indexAnalyzers, indexSettings, idFieldDataEnabled);
130130
this.documentParser = new DocumentParser(xContentRegistry, parserContextFunction);
131131
Map<String, MetadataFieldMapper.TypeParser> metadataMapperParsers =
132132
mapperRegistry.getMetadataMapperParsers(indexSettings.getIndexVersionCreated());

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

Lines changed: 9 additions & 9 deletions
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

Lines changed: 45 additions & 14 deletions
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 {
@@ -146,8 +156,7 @@ public Mapper.Builder parse(String name, Map<String, Object> node, ParserContext
146156
}
147157

148158
@SuppressWarnings("unchecked")
149-
protected boolean processField(RootObjectMapper.Builder builder, String fieldName, Object fieldNode,
150-
ParserContext parserContext) {
159+
private boolean processField(RootObjectMapper.Builder builder, String fieldName, Object fieldNode, ParserContext parserContext) {
151160
if (fieldName.equals("date_formats") || fieldName.equals("dynamic_date_formats")) {
152161
if (fieldNode instanceof List) {
153162
List<DateFormatter> formatters = new ArrayList<>();
@@ -190,10 +199,8 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
190199
String templateName = entry.getKey();
191200
Map<String, Object> templateParams = (Map<String, Object>) entry.getValue();
192201
DynamicTemplate template = DynamicTemplate.parse(templateName, templateParams);
193-
if (template != null) {
194-
validateDynamicTemplate(parserContext, template);
195-
templates.add(template);
196-
}
202+
validateDynamicTemplate(parserContext, template);
203+
templates.add(template);
197204
}
198205
builder.dynamicTemplates(templates);
199206
return true;
@@ -203,6 +210,13 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
203210
} else if (fieldName.equals("numeric_detection")) {
204211
builder.numericDetection = new Explicit<>(nodeBooleanValue(fieldNode, "numeric_detection"), true);
205212
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+
}
206220
}
207221
return false;
208222
}
@@ -212,11 +226,14 @@ protected boolean processField(RootObjectMapper.Builder builder, String fieldNam
212226
private Explicit<Boolean> dateDetection;
213227
private Explicit<Boolean> numericDetection;
214228
private Explicit<DynamicTemplate[]> dynamicTemplates;
229+
private final Map<String, RuntimeFieldType> runtimeFieldTypes;
215230

216231
RootObjectMapper(String name, Explicit<Boolean> enabled, Dynamic dynamic, Map<String, Mapper> mappers,
232+
Map<String, RuntimeFieldType> runtimeFieldTypes,
217233
Explicit<DateFormatter[]> dynamicDateTimeFormatters, Explicit<DynamicTemplate[]> dynamicTemplates,
218234
Explicit<Boolean> dateDetection, Explicit<Boolean> numericDetection, Version indexCreatedVersion) {
219235
super(name, name, enabled, Nested.NO, dynamic, mappers, indexCreatedVersion);
236+
this.runtimeFieldTypes = runtimeFieldTypes;
220237
this.dynamicTemplates = dynamicTemplates;
221238
this.dynamicDateTimeFormatters = dynamicDateTimeFormatters;
222239
this.dateDetection = dateDetection;
@@ -236,23 +253,26 @@ public ObjectMapper mappingUpdate(Mapper mapper) {
236253
return update;
237254
}
238255

239-
public boolean dateDetection() {
256+
boolean dateDetection() {
240257
return this.dateDetection.value();
241258
}
242259

243-
public boolean numericDetection() {
260+
boolean numericDetection() {
244261
return this.numericDetection.value();
245262
}
246263

247-
public DateFormatter[] dynamicDateTimeFormatters() {
264+
DateFormatter[] dynamicDateTimeFormatters() {
248265
return dynamicDateTimeFormatters.value();
249266
}
250267

251-
public DynamicTemplate[] dynamicTemplates() {
268+
DynamicTemplate[] dynamicTemplates() {
252269
return dynamicTemplates.value();
253270
}
254271

255-
@SuppressWarnings("rawtypes")
272+
Collection<RuntimeFieldType> runtimeFieldTypes() {
273+
return runtimeFieldTypes.values();
274+
}
275+
256276
public Mapper.Builder findTemplateBuilder(ParseContext context, String name, XContentFieldType matchType) {
257277
return findTemplateBuilder(context, name, matchType, null);
258278
}
@@ -268,7 +288,6 @@ public Mapper.Builder findTemplateBuilder(ParseContext context, String name, Dat
268288
* @param dateFormat a dateformatter to use if the type is a date, null if not a date or is using the default format
269289
* @return a mapper builder, or null if there is no template for such a field
270290
*/
271-
@SuppressWarnings("rawtypes")
272291
private Mapper.Builder findTemplateBuilder(ParseContext context, String name, XContentFieldType matchType, DateFormatter dateFormat) {
273292
DynamicTemplate dynamicTemplate = findTemplate(context.path(), name, matchType);
274293
if (dynamicTemplate == null) {
@@ -331,6 +350,8 @@ protected void doMerge(ObjectMapper mergeWith, MergeReason reason) {
331350
this.dynamicTemplates = mergeWithObject.dynamicTemplates;
332351
}
333352
}
353+
354+
this.runtimeFieldTypes.putAll(mergeWithObject.runtimeFieldTypes);
334355
}
335356

336357
@Override
@@ -361,6 +382,16 @@ protected void doXContent(XContentBuilder builder, ToXContent.Params params) thr
361382
if (numericDetection.explicit() || includeDefaults) {
362383
builder.field("numeric_detection", numericDetection.value());
363384
}
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+
}
364395
}
365396

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

0 commit comments

Comments
 (0)