diff --git a/core/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java b/core/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java index 34dce15dca5..17d2c9124f6 100644 --- a/core/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java +++ b/core/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java @@ -480,7 +480,7 @@ final class DocumentParser { if (objectMapper != null) { parseObjectOrField(context, objectMapper); } else { - ObjectMapper.Dynamic dynamic = dynamicOrDefault(mapper, context.root().dynamic()); + ObjectMapper.Dynamic dynamic = dynamicOrDefault(mapper, context); if (dynamic == ObjectMapper.Dynamic.STRICT) { throw new StrictDynamicMappingException(mapper.fullPath(), currentFieldName); } else if (dynamic == ObjectMapper.Dynamic.TRUE) { @@ -519,7 +519,7 @@ final class DocumentParser { } } else { - ObjectMapper.Dynamic dynamic = dynamicOrDefault(parentMapper, context.root().dynamic()); + ObjectMapper.Dynamic dynamic = dynamicOrDefault(parentMapper, context); if (dynamic == ObjectMapper.Dynamic.STRICT) { throw new StrictDynamicMappingException(parentMapper.fullPath(), arrayFieldName); } else if (dynamic == ObjectMapper.Dynamic.TRUE) { @@ -794,7 +794,7 @@ final class DocumentParser { } private static void parseDynamicValue(final ParseContext context, ObjectMapper parentMapper, String currentFieldName, XContentParser.Token token) throws IOException { - ObjectMapper.Dynamic dynamic = dynamicOrDefault(parentMapper, context.root().dynamic()); + ObjectMapper.Dynamic dynamic = dynamicOrDefault(parentMapper, context); if (dynamic == ObjectMapper.Dynamic.STRICT) { throw new StrictDynamicMappingException(parentMapper.fullPath(), currentFieldName); } @@ -867,7 +867,7 @@ final class DocumentParser { mapper = context.docMapper().objectMappers().get(context.path().pathAsText(paths[i])); if (mapper == null) { // One mapping is missing, check if we are allowed to create a dynamic one. - ObjectMapper.Dynamic dynamic = dynamicOrDefault(parent, context.root().dynamic()); + ObjectMapper.Dynamic dynamic = dynamicOrDefault(parent, context); switch (dynamic) { case STRICT: @@ -899,10 +899,26 @@ final class DocumentParser { } } - private static ObjectMapper.Dynamic dynamicOrDefault(ObjectMapper parentMapper, ObjectMapper.Dynamic dynamicDefault) { + // find what the dynamic setting is given the current parse context and parent + private static ObjectMapper.Dynamic dynamicOrDefault(ObjectMapper parentMapper, ParseContext context) { ObjectMapper.Dynamic dynamic = parentMapper.dynamic(); + while (dynamic == null) { + int lastDotNdx = parentMapper.name().lastIndexOf('.'); + if (lastDotNdx == -1) { + // no dot means we the parent is the root, so just delegate to the default outside the loop + break; + } + String parentName = parentMapper.name().substring(0, lastDotNdx); + parentMapper = context.docMapper().objectMappers().get(parentName); + if (parentMapper == null) { + // If parentMapper is ever null, it means the parent of the current mapper was dynamically created. + // But in order to be created dynamically, the dynamic setting of that parent was necessarily true + return ObjectMapper.Dynamic.TRUE; + } + dynamic = parentMapper.dynamic(); + } if (dynamic == null) { - return dynamicDefault == null ? ObjectMapper.Dynamic.TRUE : dynamicDefault; + return context.root().dynamic() == null ? ObjectMapper.Dynamic.TRUE : context.root().dynamic(); } return dynamic; } diff --git a/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java b/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java index 849c71fc7a8..9319294c31d 100644 --- a/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java +++ b/core/src/test/java/org/elasticsearch/index/mapper/DocumentParserTests.java @@ -98,6 +98,65 @@ public class DocumentParserTests extends ESSingleNodeTestCase { assertEquals("789", values[2]); } + public void testPropagateDynamicWithExistingMapper() throws Exception { + DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser(); + String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") + .field("dynamic", false) + .startObject("properties") + .startObject("foo") + .field("type", "object") + .field("dynamic", true) + .startObject("properties") + .endObject().endObject().endObject().endObject().string(); + DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping)); + BytesReference bytes = XContentFactory.jsonBuilder() + .startObject().startObject("foo") + .field("bar", "something") + .endObject().endObject().bytes(); + ParsedDocument doc = mapper.parse("test", "type", "1", bytes); + assertNotNull(doc.dynamicMappingsUpdate()); + assertNotNull(doc.rootDoc().getField("foo.bar")); + } + + public void testPropagateDynamicWithDynamicMapper() throws Exception { + DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser(); + String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") + .field("dynamic", false) + .startObject("properties") + .startObject("foo") + .field("type", "object") + .field("dynamic", true) + .startObject("properties") + .endObject().endObject().endObject().endObject().string(); + DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping)); + BytesReference bytes = XContentFactory.jsonBuilder() + .startObject().startObject("foo").startObject("bar") + .field("baz", "something") + .endObject().endObject().endObject().bytes(); + ParsedDocument doc = mapper.parse("test", "type", "1", bytes); + assertNotNull(doc.dynamicMappingsUpdate()); + assertNotNull(doc.rootDoc().getField("foo.bar.baz")); + } + + public void testDynamicRootFallback() throws Exception { + DocumentMapperParser mapperParser = createIndex("test").mapperService().documentMapperParser(); + String mapping = XContentFactory.jsonBuilder().startObject().startObject("type") + .field("dynamic", false) + .startObject("properties") + .startObject("foo") + .field("type", "object") + .startObject("properties") + .endObject().endObject().endObject().endObject().string(); + DocumentMapper mapper = mapperParser.parse("type", new CompressedXContent(mapping)); + BytesReference bytes = XContentFactory.jsonBuilder() + .startObject().startObject("foo") + .field("bar", "something") + .endObject().endObject().bytes(); + ParsedDocument doc = mapper.parse("test", "type", "1", bytes); + assertNull(doc.dynamicMappingsUpdate()); + assertNull(doc.rootDoc().getField("foo.bar")); + } + DocumentMapper createDummyMapping(MapperService mapperService) throws Exception { String mapping = jsonBuilder().startObject().startObject("type").startObject("properties") .startObject("y").field("type", "object").endObject()