Fix missing intermediate object error when applying dynamic template#87622
Fix missing intermediate object error when applying dynamic template#87622javanna merged 8 commits intoelastic:masterfrom
Conversation
a3a8981 to
f73fa0d
Compare
f73fa0d to
eb82193
Compare
|
Pinging @elastic/es-search (Team:Search) |
|
Hi @javanna, I've created a changelog YAML for you. |
When we apply dynamic mappings, we iterate over all the dynamic mappers retrieved from the DocumentParserContext, which are registered while parsing the document, and for each field look up their parent (going back multiple levels if necessary), and add it to the dynamic mapping update with the added/modified sub-field. Dynamic mappers that are iterated through consist of a flat set of mappers, which can be both object mappers or field mappers. Most times, the object mappers from such set have no sub-fields as they are mapped as a result of parsing a document where the object appears for the first time, which also has sub-fields that are going to be added to the set of dynamic mappers themselves once they are parsed. There are scenarios though where a dynamic template matches an object, and defines its structure including its subobjects as well as sub-fields. In that case the dynamically mapped object may hold sub-fields as well as define non-default values for dynamic, enabled or subobjects. The described situation was not well covered in tests so far, and is currently affected by a bug introduced with elastic#81449. When an object mapper is dynamically mapped, it is added to the map of dynamic object mappers, which makes it discoverable for sub-fields that will need to be added to it later, as well as to the set of dynamic mappers so that it becomes part of the mappings in case the document being parsed defines no sub-fields for it. What is missing is to recursively add its sub-fields to the dynamic object mappers. As a result of missing this step, intermediate objects that were dynamically mapped are not made discoverable which causes a cryptic "Missing intermediate object" error. This commit fixed the problem by recursively registering inner sub-objects to the dynamic mappers whenever an object mapper is added to the dynamic mappers. It also changes the "missing intermediate object" error to make it more evident that it's an internal error and not a user error: it is now an IllegalStateException instead of an IllegalArgumentException. Closes elastic#87513
3a55470 to
76f0af5
Compare
| } | ||
|
|
||
| private static void parseObject(final DocumentParserContext context, ObjectMapper mapper, String currentFieldName) throws IOException { | ||
| private static void parseObject(final DocumentParserContext context, ObjectMapper parentObjectMapper, String currentFieldName) |
There was a problem hiding this comment.
all the changes in this file from now on are just renaming mapper -> parentObjectMapper
| return parent.newBuilder(context.indexSettings().getIndexVersionCreated()); | ||
| } | ||
| throw new IllegalArgumentException("Missing intermediate object " + fullChildName); | ||
| throw new IllegalStateException("Missing intermediate object " + fullName); |
There was a problem hiding this comment.
this is the only relevant change in this file: IllegalArgumentException -> IllegalStateException . All the rest is renaming child to parent.
romseygeek
left a comment
There was a problem hiding this comment.
Thanks @javanna! I left a couple of comments.
| String fullParentName = prefix == null ? parentName : prefix + "." + parentName; | ||
| ObjectMapper.Builder parentBuilder = findParentBuilder(fullParentName, context); | ||
| parentBuilder.addDynamic(name.substring(firstDotIndex + 1), fullParentName, mapper, context); | ||
| add(parentBuilder); |
There was a problem hiding this comment.
I appreciate that child maybe isn't the best name here, but I don't think that parent is very good either. Maybe immediateChild? It's a child of the object mapper that addDynamic is being called on, so I think calling it parent will just confuse things.
There was a problem hiding this comment.
++ I see what you mean.
| } | ||
|
|
||
| public void testDynamicSubobject() throws IOException { | ||
| MapperService mapperService = createMapperService(topMapping(b -> { |
There was a problem hiding this comment.
Is it worth building these mappings as a text block as well? I think it reads much more easily than xcontentbuilders
There was a problem hiding this comment.
yes, the tricky bit is that we define only part of the mappings, and part is defined in topMapping. I would leave this for another time to be honest :)
There was a problem hiding this comment.
topMapping is just {"_doc":{ ... } } though. Up to you :)
There was a problem hiding this comment.
I'd prefer to spend time on fixing dynamic object mappers lookup in the context etc. over rewriting tests that work fine :)
| if (child != null) { | ||
| return child.newBuilder(context.indexSettings().getIndexVersionCreated()); | ||
| private static ObjectMapper.Builder findObjectBuilder(String fullName, DocumentParserContext context) { | ||
| // does the parent mapper already exist? if so, use that |
| child = context.getDynamicObjectMapper(fullChildName); | ||
| if (child != null) { | ||
| return child.newBuilder(context.indexSettings().getIndexVersionCreated()); | ||
| // has the parent mapper been added as a dynamic update already? |
| } | ||
|
|
||
| public void testDynamicSubobject() throws IOException { | ||
| MapperService mapperService = createMapperService(topMapping(b -> { |
There was a problem hiding this comment.
topMapping is just {"_doc":{ ... } } though. Up to you :)
romseygeek
left a comment
There was a problem hiding this comment.
LGTM, thanks @javanna
…lastic#87622) When we apply dynamic mappings, we iterate over all the dynamic mappers retrieved from the DocumentParserContext, which are registered while parsing the document, and for each field look up their parent (going back multiple levels if necessary), and add it to the dynamic mapping update with the added/modified sub-field. Dynamic mappers that are iterated through consist of a flat set of mappers, which can be both object mappers or field mappers. Most times, the object mappers from such set have no sub-fields as they are mapped as a result of parsing a document where the object appears for the first time, which also has sub-fields that are going to be added to the set of dynamic mappers themselves once they are parsed. There are scenarios though where a dynamic template matches an object, and defines its structure including its subobjects as well as sub-fields. In that case the dynamically mapped object may hold sub-fields as well as define non-default values for dynamic, enabled or subobjects. The described situation was not well covered in tests so far, and is currently affected by a bug introduced with elastic#81449. When an object mapper is dynamically mapped, it is added to the map of dynamic object mappers, which makes it discoverable for sub-fields that will need to be added to it later, as well as to the set of dynamic mappers so that it becomes part of the mappings in case the document being parsed defines no sub-fields for it. What is missing is to recursively add its sub-fields to the dynamic object mappers. As a result of missing this step, intermediate objects that were dynamically mapped are not made discoverable which causes a cryptic "Missing intermediate object" error. This commit fixed the problem by recursively registering inner sub-objects to the dynamic mappers whenever an object mapper is added to the dynamic mappers. It also changes the "missing intermediate object" error to make it more evident that it's an internal error and not a user error: it is now an IllegalStateException instead of an IllegalArgumentException. Closes elastic#87513
…87622) When we apply dynamic mappings, we iterate over all the dynamic mappers retrieved from the DocumentParserContext, which are registered while parsing the document, and for each field look up their parent (going back multiple levels if necessary), and add it to the dynamic mapping update with the added/modified sub-field. Dynamic mappers that are iterated through consist of a flat set of mappers, which can be both object mappers or field mappers. Most times, the object mappers from such set have no sub-fields as they are mapped as a result of parsing a document where the object appears for the first time, which also has sub-fields that are going to be added to the set of dynamic mappers themselves once they are parsed. There are scenarios though where a dynamic template matches an object, and defines its structure including its subobjects as well as sub-fields. In that case the dynamically mapped object may hold sub-fields as well as define non-default values for dynamic, enabled or subobjects. The described situation was not well covered in tests so far, and is currently affected by a bug introduced with #81449. When an object mapper is dynamically mapped, it is added to the map of dynamic object mappers, which makes it discoverable for sub-fields that will need to be added to it later, as well as to the set of dynamic mappers so that it becomes part of the mappings in case the document being parsed defines no sub-fields for it. What is missing is to recursively add its sub-fields to the dynamic object mappers. As a result of missing this step, intermediate objects that were dynamically mapped are not made discoverable which causes a cryptic "Missing intermediate object" error. This commit fixed the problem by recursively registering inner sub-objects to the dynamic mappers whenever an object mapper is added to the dynamic mappers. It also changes the "missing intermediate object" error to make it more evident that it's an internal error and not a user error: it is now an IllegalStateException instead of an IllegalArgumentException. Closes #87513
When we apply dynamic mappings, we iterate over all the dynamic mappers retrieved from the DocumentParserContext, which are registered while parsing the document, and for each field look up their parent (going back multiple levels if necessary), and add it to the dynamic mapping update with the added/modified sub-field.
Dynamic mappers that are iterated through consist of a flat set of mappers, which can be both object mappers or field mappers. Most times, the object mappers from such set have no sub-fields as they are mapped as a result of parsing a document where the object appears for the first time, which also has sub-fields that are going to be added to the set of dynamic mappers themselves once they are parsed.
There are scenarios though where a dynamic template matches an object, and defines its structure including its subobjects as well as sub-fields. In that case the dynamically mapped object may hold sub-fields as well as define non-default values for dynamic, enabled or subobjects. The described situation was not well covered in tests so far, and is currently affected by a bug introduced with #81449. When an object mapper is dynamically mapped, it is added to the map of dynamic object mappers, which makes it discoverable for sub-fields that will need to be added to it later, as well as to the set of dynamic mappers so that it becomes part of the mappings in case the document being parsed defines no sub-fields for it. What is missing is to recursively add its sub-fields to the dynamic object mappers. As a result of missing this step, intermediate objects that were dynamically mapped are not made discoverable which causes a cryptic "Missing intermediate object" error.
This commit fixed the problem by recursively registering inner sub-objects to the dynamic mappers whenever an object mapper is added to the dynamic mappers. It also changes the "missing intermediate object" error to make it more evident that it's an internal error and not a user error: it is now an IllegalStateException instead of an IllegalArgumentException.
Closes #87513