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 5793e1f32808f..7cd7952893f91 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentParser.java @@ -396,7 +396,7 @@ static void parseObjectOrField(DocumentParserContext context, Mapper mapper) thr fieldMapper.parse(context); } if (context.isWithinCopyTo() == false) { - List copyToFields = fieldMapper.copyTo().copyToFields(); + List copyToFields = context.mappingLookup().copyToFieldsForParsing(mapper.name()); if (copyToFields.isEmpty() == false) { XContentParser.Token currentToken = context.parser().currentToken(); if (currentToken.isValue() == false && currentToken != XContentParser.Token.VALUE_NULL) { @@ -696,10 +696,6 @@ private static void failIfMatchesRoutingPath(DocumentParserContext context, Stri */ private static void parseCopyFields(DocumentParserContext context, List copyToFields) throws IOException { for (String field : copyToFields) { - if (context.mappingLookup().inferenceFields().get(field) != null) { - // ignore copy_to that targets inference fields, values are already extracted in the coordinating node to perform inference. - continue; - } // In case of a hierarchy of nested documents, we need to figure out // which document the field should go to LuceneDocument targetDoc = null; diff --git a/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java b/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java index 5e3dbe9590b99..4dfdca9abcaa0 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java @@ -14,6 +14,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -36,6 +37,12 @@ final class FieldTypeLookup { */ private final Map> fieldToCopiedFields; + /** + * Contains the effective copy_to fields for each field. Fields that don't have + * to be actually parsed by the target copy_to fields will be removed from this list. + */ + private final Map> copyToFields; + private final int maxParentPathDots; FieldTypeLookup( @@ -48,6 +55,8 @@ final class FieldTypeLookup { final Map fullSubfieldNameToParentPath = new HashMap<>(); final Map dynamicFieldTypes = new HashMap<>(); final Map> fieldToCopiedFields = new HashMap<>(); + final Map> effectiveCopyTo = new HashMap<>(); + final Set inferenceFieldMappers = new HashSet<>(); for (FieldMapper fieldMapper : fieldMappers) { String fieldName = fieldMapper.name(); MappedFieldType fieldType = fieldMapper.fieldType(); @@ -56,7 +65,9 @@ final class FieldTypeLookup { if (fieldType instanceof DynamicFieldType) { dynamicFieldTypes.put(fieldType.name(), (DynamicFieldType) fieldType); } - for (String targetField : fieldMapper.copyTo().copyToFields()) { + List copyToFields = fieldMapper.copyTo().copyToFields(); + effectiveCopyTo.put(fieldName, copyToFields); + for (String targetField : copyToFields) { Set sourcePath = fieldToCopiedFields.get(targetField); if (sourcePath == null) { Set copiedFields = new HashSet<>(); @@ -65,6 +76,14 @@ final class FieldTypeLookup { } fieldToCopiedFields.get(targetField).add(fieldName); } + if (fieldMapper instanceof InferenceFieldMapper) { + inferenceFieldMappers.add(fieldName); + } + } + + // Remove all inference field mappers from the effective copy to list + for (FieldMapper fieldMapper : fieldMappers) { + effectiveCopyTo.get(fieldMapper.name()).removeAll(inferenceFieldMappers); } int maxParentPathDots = 0; @@ -97,6 +116,8 @@ final class FieldTypeLookup { // make values into more compact immutable sets to save memory fieldToCopiedFields.entrySet().forEach(e -> e.setValue(Set.copyOf(e.getValue()))); this.fieldToCopiedFields = Map.copyOf(fieldToCopiedFields); + effectiveCopyTo.entrySet().forEach(e -> e.setValue(List.copyOf(e.getValue()))); + this.copyToFields = Map.copyOf(effectiveCopyTo); } public static int dotCount(String path) { @@ -205,6 +226,10 @@ Set sourcePaths(String field) { return fieldToCopiedFields.containsKey(resolvedField) ? fieldToCopiedFields.get(resolvedField) : Set.of(resolvedField); } + List copyToFields(String field) { + return copyToFields.getOrDefault(field, Collections.emptyList()); + } + /** * If field is a leaf multi-field return the path to the parent field. Otherwise, return null. */ diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java b/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java index bf879f30e5a29..271eca2be870b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java @@ -135,6 +135,7 @@ private MappingLookup( this.mapping = mapping; Map fieldMappers = new HashMap<>(); Map objects = new HashMap<>(); + Map> effectiveCopyTo = new HashMap<>(); List nestedMappers = new ArrayList<>(); for (ObjectMapper mapper : objectMappers) { @@ -448,6 +449,19 @@ public Set sourcePaths(String field) { return fieldTypesLookup().sourcePaths(field); } + /** + * Retrieves the effective copy to fields for the given field that should be parsed. Some fields may skip + * parsing as the value may have already been calculated by another process and doesn't need to be parsed + * by the document parser + * + * @param field the field for which to look up the _source path. Note that the field. Note that the field should be a + * concrete field and *not* an alias. + * @return A list of field names that the field should be copied to for parsing + */ + public List copyToFieldsForParsing(String field) { + return fieldTypesLookup().copyToFields(field); + } + /** * If field is a leaf multi-field return the path to the parent field. Otherwise, return null. */