diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java index 04c6feaaf66b7..ea42f83d7c040 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java @@ -263,6 +263,7 @@ static Mapping createDynamicUpdate(MappingLookup mappingLookup, RootObjectMapper root; if (dynamicMappers.isEmpty() == false) { root = createDynamicUpdate(mappingLookup, dynamicMappers); + root.fixRedundantIncludes(); } else { root = mappingLookup.getMapping().getRoot().copyAndReset(); } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java index 5ed10f6aa3f3b..8b9bb8717fdd2 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/NestedObjectMapperTests.java @@ -412,6 +412,44 @@ public void testMultipleLevelsIncludeRoot1() throws Exception { assertThat(fields.size(), equalTo(new HashSet<>(fields).size())); } + public void testRecursiveIncludeInParent() throws IOException { + + // if we have a nested hierarchy, and all nested mappers have 'include_in_parent' + // set to 'true', then values from the grandchild nodes should be copied all the + // way up the hierarchy and into the root document, even if 'include_in_root' has + // explicitly been set to 'false'. + + MapperService mapperService = createMapperService(mapping(b -> { + b.startObject("nested1"); + b.field("type", "nested"); + b.field("include_in_parent", true); + b.field("include_in_root", false); + b.startObject("properties"); + b.startObject("nested1_id").field("type", "keyword").endObject(); + b.startObject("nested2"); + b.field("type", "nested"); + b.field("include_in_parent", true); + b.field("include_in_root", false); + b.startObject("properties"); + b.startObject("nested2_id").field("type", "keyword").endObject(); + b.endObject(); + b.endObject(); + b.endObject(); + b.endObject(); + })); + + ParsedDocument doc = mapperService.documentMapper().parse(source(b -> { + b.startObject("nested1"); + b.field("nested1_id", "1"); + b.startObject("nested2"); + b.field("nested2_id", "2"); + b.endObject(); + b.endObject(); + })); + + assertNotNull(doc.rootDoc().getField("nested1.nested2.nested2_id")); + } + /** * Same as {@link NestedObjectMapperTests#testMultipleLevelsIncludeRoot1()} but tests for the * case where the transitive {@code include_in_parent} and redundant {@code include_in_root} @@ -801,4 +839,34 @@ public void testMergeNestedMappings() throws IOException { }))); assertEquals("the [include_in_root] parameter can't be updated on a nested object mapping", e2.getMessage()); } + + public void testMergeNestedMappingsFromDynamicUpdate() throws IOException { + + // Check that dynamic mappings have redundant includes removed + + MapperService mapperService = createMapperService(topMapping(b -> { + b.startArray("dynamic_templates"); + b.startObject(); + b.startObject("object_fields"); + b.field("match_mapping_type", "object"); + b.startObject("mapping"); + b.field("type", "nested"); + b.field("include_in_parent", true); + b.field("include_in_root", true); + b.endObject(); + b.field("match", "*"); + b.endObject(); + b.endObject(); + b.endArray(); + })); + + ParsedDocument doc = mapperService.documentMapper().parse(source(b -> b.startObject("object").endObject())); + + merge(mapperService, Strings.toString(doc.dynamicMappingsUpdate())); + merge(mapperService, Strings.toString(doc.dynamicMappingsUpdate())); + + assertThat( + Strings.toString(mapperService.documentMapper().mapping()), + containsString("\"properties\":{\"object\":{\"type\":\"nested\",\"include_in_parent\":true}}")); + } }