Skip to content

Commit 095d1fc

Browse files
authored
Propagate MapperBuilderContext across merge calls (#86946)
With #86166 we have added support for leaf fields that have dots in their names, without needing to expand their path to intermediate objects. That means that for instance a host.name keyword field mapper can be held directly by the root object mapper if the root has subobjects set to false. This opens up for situations where a field name containing dots can't necessarily be split into a leaf name and a parent path made of intermediate object mappers. In the field mapper merge code, we build the full path of the merged field making that now incorrect assumption, which causes the result of merged leaf fields to have incorrect full path. This is a bug although it got unnoticed so far as the full path of a field mapper is not so widely used compared to its leaf name (returned by the simpleName method). One place where the full path is used is when sorting the child mappers in ObjectMapper#serializeMappers which was causing MapperService#assertRefreshIsNotNeeded to trip as the result of the merge has fields in a different order compared to the incoming mappings. With this fix, we add a MapperBuilderContext argument to the different merge methods, propagate the MapperBuilderContext in all the merge calls and create a child context when needed, meaning whenever merging object mappers, so that their children mappers are created with the proper full path.
1 parent 954d288 commit 095d1fc

14 files changed

+199
-65
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,14 @@ public DocumentDimensions getDimensions() {
364364

365365
public abstract ContentPath path();
366366

367+
public final MapperBuilderContext createMapperBuilderContext() {
368+
String p = path().pathAsText("");
369+
if (p.endsWith(".")) {
370+
p = p.substring(0, p.length() - 1);
371+
}
372+
return new MapperBuilderContext(p);
373+
}
374+
367375
public abstract XContentParser parser();
368376

369377
public abstract LuceneDocument rootDoc();

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,15 +159,17 @@ void createDynamicFieldFromValue(final DocumentParserContext context, XContentPa
159159
*/
160160
static Mapper createDynamicObjectMapper(DocumentParserContext context, String name) {
161161
Mapper mapper = createObjectMapperFromTemplate(context, name);
162-
return mapper != null ? mapper : new ObjectMapper.Builder(name).enabled(true).build(MapperBuilderContext.forPath(context.path()));
162+
return mapper != null
163+
? mapper
164+
: new ObjectMapper.Builder(name).enabled(ObjectMapper.Defaults.ENABLED).build(context.createMapperBuilderContext());
163165
}
164166

165167
/**
166168
* Returns a dynamically created object mapper, based exclusively on a matching dynamic template, null otherwise.
167169
*/
168170
static Mapper createObjectMapperFromTemplate(DocumentParserContext context, String name) {
169171
Mapper.Builder templateBuilder = findTemplateBuilderForObject(context, name);
170-
return templateBuilder == null ? null : templateBuilder.build(MapperBuilderContext.forPath(context.path()));
172+
return templateBuilder == null ? null : templateBuilder.build(context.createMapperBuilderContext());
171173
}
172174

173175
/**
@@ -302,7 +304,7 @@ private static final class Concrete implements Strategy {
302304
}
303305

304306
void createDynamicField(Mapper.Builder builder, DocumentParserContext context) throws IOException {
305-
Mapper mapper = builder.build(MapperBuilderContext.forPath(context.path()));
307+
Mapper mapper = builder.build(context.createMapperBuilderContext());
306308
context.addDynamicMapper(mapper);
307309
parseField.accept(context, mapper);
308310
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public String path() {
5555
}
5656

5757
@Override
58-
public Mapper merge(Mapper mergeWith) {
58+
public Mapper merge(Mapper mergeWith, MapperBuilderContext mapperBuilderContext) {
5959
if ((mergeWith instanceof FieldAliasMapper) == false) {
6060
throw new IllegalArgumentException(
6161
"Cannot merge a field alias mapping [" + name() + "] with a mapping that is not for a field alias."

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

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ private static void checkNestedScopeCompatibility(String source, String target)
321321
public abstract Builder getMergeBuilder();
322322

323323
@Override
324-
public final FieldMapper merge(Mapper mergeWith) {
324+
public final FieldMapper merge(Mapper mergeWith, MapperBuilderContext mapperBuilderContext) {
325325
if (mergeWith == this) {
326326
return this;
327327
}
@@ -343,9 +343,9 @@ public final FieldMapper merge(Mapper mergeWith) {
343343
return (FieldMapper) mergeWith;
344344
}
345345
Conflicts conflicts = new Conflicts(name());
346-
builder.merge((FieldMapper) mergeWith, conflicts);
346+
builder.merge((FieldMapper) mergeWith, conflicts, mapperBuilderContext);
347347
conflicts.check();
348-
return builder.build(MapperBuilderContext.forPath(Builder.parentPath(name())));
348+
return builder.build(mapperBuilderContext);
349349
}
350350

351351
protected void checkIncomingMergeType(FieldMapper mergeWith) {
@@ -408,7 +408,7 @@ public Builder update(FieldMapper toMerge, MapperBuilderContext context) {
408408
add(toMerge);
409409
} else {
410410
FieldMapper existing = mapperBuilders.get(toMerge.simpleName()).apply(context);
411-
add(existing.merge(toMerge));
411+
add(existing.merge(toMerge, context));
412412
}
413413
return this;
414414
}
@@ -1138,12 +1138,13 @@ public Builder init(FieldMapper initializer) {
11381138
return this;
11391139
}
11401140

1141-
protected void merge(FieldMapper in, Conflicts conflicts) {
1141+
protected void merge(FieldMapper in, Conflicts conflicts, MapperBuilderContext mapperBuilderContext) {
11421142
for (Parameter<?> param : getParameters()) {
11431143
param.merge(in, conflicts);
11441144
}
1145+
MapperBuilderContext childContext = mapperBuilderContext.createChildContext(in.simpleName());
11451146
for (FieldMapper newSubField : in.multiFields.mappers) {
1146-
multiFieldsBuilder.update(newSubField, MapperBuilderContext.forPath(parentPath(newSubField.name())));
1147+
multiFieldsBuilder.update(newSubField, childContext);
11471148
}
11481149
this.copyTo.reset(in.copyTo);
11491150
validate();

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ protected Builder(String name) {
2525
this.name = internFieldName(name);
2626
}
2727

28+
// TODO rename this to leafName?
2829
public String name() {
2930
return this.name;
3031
}
@@ -53,11 +54,13 @@ public Mapper(String simpleName) {
5354

5455
/** Returns the simple name, which identifies this mapper against other mappers at the same level in the mappers hierarchy
5556
* TODO: make this protected once Mapper and FieldMapper are merged together */
57+
// TODO rename this to leafName?
5658
public final String simpleName() {
5759
return simpleName;
5860
}
5961

6062
/** Returns the canonical name which uniquely identifies the mapper against other mappers in a type. */
63+
// TODO rename this to fullPath???
6164
public abstract String name();
6265

6366
/**
@@ -67,7 +70,7 @@ public final String simpleName() {
6770

6871
/** Return the merge of {@code mergeWith} into this.
6972
* Both {@code this} and {@code mergeWith} will be left unmodified. */
70-
public abstract Mapper merge(Mapper mergeWith);
73+
public abstract Mapper merge(Mapper mergeWith, MapperBuilderContext mapperBuilderContext);
7174

7275
/**
7376
* Validate any cross-field references made by this mapper

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

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,26 @@
1010

1111
import org.elasticsearch.common.Strings;
1212

13+
import java.util.Objects;
14+
1315
/**
1416
* Holds context for building Mapper objects from their Builders
1517
*/
16-
public class MapperBuilderContext {
18+
public final class MapperBuilderContext {
1719

1820
/**
1921
* The root context, to be used when building a tree of mappers
2022
*/
21-
public static final MapperBuilderContext ROOT = new MapperBuilderContext(null);
22-
23-
// TODO remove this
24-
public static MapperBuilderContext forPath(ContentPath path) {
25-
String p = path.pathAsText("");
26-
if (p.endsWith(".")) {
27-
p = p.substring(0, p.length() - 1);
28-
}
29-
return new MapperBuilderContext(p);
30-
}
23+
public static final MapperBuilderContext ROOT = new MapperBuilderContext();
3124

3225
private final String path;
3326

34-
private MapperBuilderContext(String path) {
35-
this.path = path;
27+
private MapperBuilderContext() {
28+
this.path = null;
29+
}
30+
31+
MapperBuilderContext(String path) {
32+
this.path = Objects.requireNonNull(path);
3633
}
3734

3835
/**
@@ -47,7 +44,7 @@ public MapperBuilderContext createChildContext(String name) {
4744
/**
4845
* Builds the full name of the field, taking into account parent objects
4946
*/
50-
public final String buildFullName(String name) {
47+
public String buildFullName(String name) {
5148
if (Strings.isEmpty(path)) {
5249
return name;
5350
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ Mapping mappingUpdate(RootObjectMapper rootObjectMapper) {
127127
* @return the resulting merged mapping.
128128
*/
129129
Mapping merge(Mapping mergeWith, MergeReason reason) {
130-
RootObjectMapper mergedRoot = root.merge(mergeWith.root, reason);
130+
RootObjectMapper mergedRoot = root.merge(mergeWith.root, reason, MapperBuilderContext.ROOT);
131131

132132
// When merging metadata fields as part of applying an index template, new field definitions
133133
// completely overwrite existing ones instead of being merged. This behavior matches how we
@@ -139,7 +139,7 @@ Mapping merge(Mapping mergeWith, MergeReason reason) {
139139
if (mergeInto == null || reason == MergeReason.INDEX_TEMPLATE) {
140140
merged = metaMergeWith;
141141
} else {
142-
merged = (MetadataFieldMapper) mergeInto.merge(metaMergeWith);
142+
merged = (MetadataFieldMapper) mergeInto.merge(metaMergeWith, MapperBuilderContext.ROOT);
143143
}
144144
mergedMetadataMappers.put(merged.getClass(), merged);
145145
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
170170
}
171171

172172
@Override
173-
public ObjectMapper merge(Mapper mergeWith, MapperService.MergeReason reason) {
173+
public ObjectMapper merge(Mapper mergeWith, MapperService.MergeReason reason, MapperBuilderContext mapperBuilderContext) {
174174
if ((mergeWith instanceof NestedObjectMapper) == false) {
175175
throw new IllegalArgumentException("can't merge a non nested mapping [" + mergeWith.name() + "] with a nested mapping");
176176
}
@@ -191,7 +191,7 @@ public ObjectMapper merge(Mapper mergeWith, MapperService.MergeReason reason) {
191191
throw new MapperException("the [include_in_root] parameter can't be updated on a nested object mapping");
192192
}
193193
}
194-
toMerge.doMerge(mergeWithObject, reason);
194+
toMerge.doMerge(mergeWithObject, reason, mapperBuilderContext);
195195
return toMerge;
196196
}
197197

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

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ protected final Map<String, Mapper> buildMappers(boolean root, MapperBuilderCont
163163
}
164164
Mapper existing = mappers.get(mapper.simpleName());
165165
if (existing != null) {
166-
mapper = existing.merge(mapper);
166+
mapper = existing.merge(mapper, mapperBuilderContext);
167167
}
168168
mappers.put(mapper.simpleName(), mapper);
169169
}
@@ -414,8 +414,8 @@ public final boolean subobjects() {
414414
}
415415

416416
@Override
417-
public ObjectMapper merge(Mapper mergeWith) {
418-
return merge(mergeWith, MergeReason.MAPPING_UPDATE);
417+
public ObjectMapper merge(Mapper mergeWith, MapperBuilderContext mapperBuilderContext) {
418+
return merge(mergeWith, MergeReason.MAPPING_UPDATE, mapperBuilderContext);
419419
}
420420

421421
@Override
@@ -425,7 +425,7 @@ public void validate(MappingLookup mappers) {
425425
}
426426
}
427427

428-
public ObjectMapper merge(Mapper mergeWith, MergeReason reason) {
428+
public ObjectMapper merge(Mapper mergeWith, MergeReason reason, MapperBuilderContext mapperBuilderContext) {
429429
if ((mergeWith instanceof ObjectMapper) == false) {
430430
throw new IllegalArgumentException("can't merge a non object mapping [" + mergeWith.name() + "] with an object mapping");
431431
}
@@ -435,11 +435,11 @@ public ObjectMapper merge(Mapper mergeWith, MergeReason reason) {
435435
}
436436
ObjectMapper mergeWithObject = (ObjectMapper) mergeWith;
437437
ObjectMapper merged = clone();
438-
merged.doMerge(mergeWithObject, reason);
438+
merged.doMerge(mergeWithObject, reason, mapperBuilderContext);
439439
return merged;
440440
}
441441

442-
protected void doMerge(final ObjectMapper mergeWith, MergeReason reason) {
442+
protected void doMerge(final ObjectMapper mergeWith, MergeReason reason, MapperBuilderContext mapperBuilderContext) {
443443

444444
if (mergeWith.dynamic != null) {
445445
this.dynamic = mergeWith.dynamic;
@@ -469,7 +469,8 @@ protected void doMerge(final ObjectMapper mergeWith, MergeReason reason) {
469469
if (mergeIntoMapper == null) {
470470
merged = mergeWithMapper;
471471
} else if (mergeIntoMapper instanceof ObjectMapper objectMapper) {
472-
merged = objectMapper.merge(mergeWithMapper, reason);
472+
MapperBuilderContext childContext = mapperBuilderContext.createChildContext(objectMapper.simpleName());
473+
merged = objectMapper.merge(mergeWithMapper, reason, childContext);
473474
} else {
474475
assert mergeIntoMapper instanceof FieldMapper || mergeIntoMapper instanceof FieldAliasMapper;
475476
if (mergeWithMapper instanceof ObjectMapper) {
@@ -483,7 +484,7 @@ protected void doMerge(final ObjectMapper mergeWith, MergeReason reason) {
483484
if (reason == MergeReason.INDEX_TEMPLATE) {
484485
merged = mergeWithMapper;
485486
} else {
486-
merged = mergeIntoMapper.merge(mergeWithMapper);
487+
merged = mergeIntoMapper.merge(mergeWithMapper, mapperBuilderContext);
487488
}
488489
}
489490
if (mergedMappers == null) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,10 +65,10 @@ public FieldMapper.Builder init(FieldMapper initializer) {
6565
}
6666

6767
@Override
68-
protected void merge(FieldMapper in, Conflicts conflicts) {
68+
protected void merge(FieldMapper in, Conflicts conflicts, MapperBuilderContext mapperBuilderContext) {
6969
assert in instanceof PlaceHolderFieldMapper;
7070
unknownParams.putAll(((PlaceHolderFieldMapper) in).unknownParams);
71-
super.merge(in, conflicts);
71+
super.merge(in, conflicts, mapperBuilderContext);
7272
}
7373

7474
@Override

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -322,13 +322,13 @@ RuntimeField getRuntimeField(String name) {
322322
}
323323

324324
@Override
325-
public RootObjectMapper merge(Mapper mergeWith, MergeReason reason) {
326-
return (RootObjectMapper) super.merge(mergeWith, reason);
325+
public RootObjectMapper merge(Mapper mergeWith, MergeReason reason, MapperBuilderContext mapperBuilderContext) {
326+
return (RootObjectMapper) super.merge(mergeWith, reason, mapperBuilderContext);
327327
}
328328

329329
@Override
330-
protected void doMerge(ObjectMapper mergeWith, MergeReason reason) {
331-
super.doMerge(mergeWith, reason);
330+
protected void doMerge(ObjectMapper mergeWith, MergeReason reason, MapperBuilderContext mapperBuilderContext) {
331+
super.doMerge(mergeWith, reason, mapperBuilderContext);
332332
RootObjectMapper mergeWithObject = (RootObjectMapper) mergeWith;
333333
if (mergeWithObject.numericDetection.explicit()) {
334334
this.numericDetection = mergeWithObject.numericDetection;

server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -158,9 +158,7 @@ public void testFieldAliasWithDifferentNestedScopes() {
158158
}
159159

160160
private static FieldMapper createFieldMapper(String parent, String name) {
161-
return new BooleanFieldMapper.Builder(name, ScriptCompiler.NONE, Version.CURRENT).build(
162-
MapperBuilderContext.forPath(new ContentPath(parent))
163-
);
161+
return new BooleanFieldMapper.Builder(name, ScriptCompiler.NONE, Version.CURRENT).build(new MapperBuilderContext(parent));
164162
}
165163

166164
private static ObjectMapper createObjectMapper(String name) {

0 commit comments

Comments
 (0)