-
Notifications
You must be signed in to change notification settings - Fork 24.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Create aliases for PassThroughObjectMapper subfields in FieldTypeLookup #106829
Changes from 10 commits
009ee70
c54fd29
6ec8699
05a6016
cbd6076
c084c31
5109d56
145ba0d
d867e30
0f9b08d
7f2dc2a
45aa214
3cd0f1c
ba3aecc
a72f16d
201b882
f700d59
8553b99
e1241c5
879afde
20ed13d
1a5823c
f7e78aa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -38,9 +39,14 @@ final class FieldTypeLookup { | |
|
||
private final int maxParentPathDots; | ||
|
||
FieldTypeLookup(Collection<FieldMapper> fieldMappers, Collection<FieldAliasMapper> fieldAliasMappers) { | ||
this(fieldMappers, fieldAliasMappers, List.of(), List.of()); | ||
} | ||
|
||
FieldTypeLookup( | ||
Collection<FieldMapper> fieldMappers, | ||
Collection<FieldAliasMapper> fieldAliasMappers, | ||
Collection<PassThroughObjectMapper> passThroughMappers, | ||
Collection<RuntimeField> runtimeFields | ||
) { | ||
|
||
|
@@ -86,6 +92,35 @@ final class FieldTypeLookup { | |
} | ||
} | ||
|
||
// Pass-though subfields can be referenced without the prefix corresponding to the | ||
// PassThroughObjectMapper name. This is achieved by adding a second reference to their | ||
// MappedFieldType using the remaining suffix. | ||
Map<String, PassThroughObjectMapper> passThroughFieldAliases = new HashMap<>(); | ||
for (PassThroughObjectMapper passThroughMapper : passThroughMappers) { | ||
for (Mapper subfield : passThroughMapper.mappers.values()) { | ||
if (subfield instanceof FieldMapper fieldMapper) { | ||
String name = fieldMapper.simpleName(); | ||
// Check for conflict between PassThroughObjectMapper subfields. | ||
PassThroughObjectMapper conflict = passThroughFieldAliases.put(name, passThroughMapper); | ||
if (conflict != null) { | ||
// Check the priorities of the conflicting objects. | ||
if (conflict.priority() > passThroughMapper.priority()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. let's reverse the priority? A field from a passthrough field with a higher priority wins. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this is the case? If the inserted object has higher priority we leave it there, otherwise the conflicting one is put back. In both cases, |
||
passThroughFieldAliases.put(name, conflict); | ||
continue; | ||
} | ||
} else if (fullNameToFieldType.containsKey(name)) { | ||
// There's an existing field or alias for the same field. | ||
continue; | ||
} | ||
MappedFieldType fieldType = fieldMapper.fieldType(); | ||
fullNameToFieldType.put(name, fieldType); | ||
if (fieldType instanceof DynamicFieldType) { | ||
dynamicFieldTypes.put(name, (DynamicFieldType) fieldType); | ||
} | ||
} | ||
} | ||
} | ||
|
||
for (MappedFieldType fieldType : RuntimeField.collectFieldTypes(runtimeFields).values()) { | ||
// this will override concrete fields with runtime fields that have the same name | ||
fullNameToFieldType.put(fieldType.name(), fieldType); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -40,7 +40,7 @@ private CacheKey() {} | |
* A lookup representing an empty mapping. It can be used to look up fields, although it won't hold any, but it does not | ||
* hold a valid {@link DocumentParser}, {@link IndexSettings} or {@link IndexAnalyzers}. | ||
*/ | ||
public static final MappingLookup EMPTY = fromMappers(Mapping.EMPTY, List.of(), List.of(), List.of()); | ||
public static final MappingLookup EMPTY = fromMappers(Mapping.EMPTY, List.of(), List.of()); | ||
|
||
private final CacheKey cacheKey = new CacheKey(); | ||
|
||
|
@@ -67,35 +67,40 @@ public static MappingLookup fromMapping(Mapping mapping) { | |
List<ObjectMapper> newObjectMappers = new ArrayList<>(); | ||
List<FieldMapper> newFieldMappers = new ArrayList<>(); | ||
List<FieldAliasMapper> newFieldAliasMappers = new ArrayList<>(); | ||
List<PassThroughObjectMapper> newPassThroughMappers = new ArrayList<>(); | ||
for (MetadataFieldMapper metadataMapper : mapping.getSortedMetadataMappers()) { | ||
if (metadataMapper != null) { | ||
newFieldMappers.add(metadataMapper); | ||
} | ||
} | ||
for (Mapper child : mapping.getRoot()) { | ||
collect(child, newObjectMappers, newFieldMappers, newFieldAliasMappers); | ||
collect(child, newObjectMappers, newFieldMappers, newFieldAliasMappers, newPassThroughMappers); | ||
} | ||
return new MappingLookup(mapping, newFieldMappers, newObjectMappers, newFieldAliasMappers); | ||
return new MappingLookup(mapping, newFieldMappers, newObjectMappers, newFieldAliasMappers, newPassThroughMappers); | ||
} | ||
|
||
private static void collect( | ||
Mapper mapper, | ||
Collection<ObjectMapper> objectMappers, | ||
Collection<FieldMapper> fieldMappers, | ||
Collection<FieldAliasMapper> fieldAliasMappers | ||
Collection<FieldAliasMapper> fieldAliasMappers, | ||
Collection<PassThroughObjectMapper> passThroughMappers | ||
) { | ||
if (mapper instanceof ObjectMapper) { | ||
objectMappers.add((ObjectMapper) mapper); | ||
} else if (mapper instanceof FieldMapper) { | ||
fieldMappers.add((FieldMapper) mapper); | ||
} else if (mapper instanceof FieldAliasMapper) { | ||
fieldAliasMappers.add((FieldAliasMapper) mapper); | ||
if (mapper instanceof PassThroughObjectMapper passThroughObjectMapper) { | ||
passThroughMappers.add(passThroughObjectMapper); | ||
objectMappers.add(passThroughObjectMapper); | ||
} else if (mapper instanceof ObjectMapper objectMapper) { | ||
objectMappers.add(objectMapper); | ||
} else if (mapper instanceof FieldMapper fieldMapper) { | ||
fieldMappers.add(fieldMapper); | ||
} else if (mapper instanceof FieldAliasMapper fieldAliasMapper) { | ||
fieldAliasMappers.add(fieldAliasMapper); | ||
} else { | ||
throw new IllegalStateException("Unrecognized mapper type [" + mapper.getClass().getSimpleName() + "]."); | ||
} | ||
|
||
for (Mapper child : mapper) { | ||
collect(child, objectMappers, fieldMappers, fieldAliasMappers); | ||
collect(child, objectMappers, fieldMappers, fieldAliasMappers, passThroughMappers); | ||
} | ||
} | ||
|
||
|
@@ -111,22 +116,29 @@ private static void collect( | |
* @param mappers the field mappers | ||
* @param objectMappers the object mappers | ||
* @param aliasMappers the field alias mappers | ||
* @param passThroughMappers the pass-through mappers | ||
* @return the newly created lookup instance | ||
*/ | ||
public static MappingLookup fromMappers( | ||
Mapping mapping, | ||
Collection<FieldMapper> mappers, | ||
Collection<ObjectMapper> objectMappers, | ||
Collection<FieldAliasMapper> aliasMappers | ||
Collection<FieldAliasMapper> aliasMappers, | ||
Collection<PassThroughObjectMapper> passThroughMappers | ||
) { | ||
return new MappingLookup(mapping, mappers, objectMappers, aliasMappers); | ||
return new MappingLookup(mapping, mappers, objectMappers, aliasMappers, passThroughMappers); | ||
} | ||
|
||
public static MappingLookup fromMappers(Mapping mapping, Collection<FieldMapper> mappers, Collection<ObjectMapper> objectMappers) { | ||
return new MappingLookup(mapping, mappers, objectMappers, List.of(), List.of()); | ||
} | ||
|
||
private MappingLookup( | ||
Mapping mapping, | ||
Collection<FieldMapper> mappers, | ||
Collection<ObjectMapper> objectMappers, | ||
Collection<FieldAliasMapper> aliasMappers | ||
Collection<FieldAliasMapper> aliasMappers, | ||
Collection<PassThroughObjectMapper> passThroughMappers | ||
) { | ||
this.totalFieldsCount = mapping.getRoot().getTotalFieldsCount(); | ||
this.mapping = mapping; | ||
|
@@ -172,13 +184,14 @@ private MappingLookup( | |
} | ||
} | ||
|
||
PassThroughObjectMapper.checkForInvalidPriorities(passThroughMappers); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure if this is called when creating or updating an index template as it doesn't affect a concrete index, yet. In any case, I think it would be prudent to add a test case for an index template with two passthrough field types that don't specify a priority to see if this gets caught. If this indeed turns out not to be the appropriate place for the validation, an alternative may be to validate this in the root object mapper. Yet another approach could be to allow duplicate priorities and then fall back to lexicographical ordering if the priority is the same. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So templates are validated in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice, thanks for double-checking! |
||
final Collection<RuntimeField> runtimeFields = mapping.getRoot().runtimeFields(); | ||
this.fieldTypeLookup = new FieldTypeLookup(mappers, aliasMappers, runtimeFields); | ||
this.fieldTypeLookup = new FieldTypeLookup(mappers, aliasMappers, passThroughMappers, runtimeFields); | ||
if (runtimeFields.isEmpty()) { | ||
// without runtime fields this is the same as the field type lookup | ||
this.indexTimeLookup = fieldTypeLookup; | ||
} else { | ||
this.indexTimeLookup = new FieldTypeLookup(mappers, aliasMappers, Collections.emptyList()); | ||
this.indexTimeLookup = new FieldTypeLookup(mappers, aliasMappers, passThroughMappers, Collections.emptyList()); | ||
} | ||
// make all fields into compact+fast immutable maps | ||
this.fieldMappers = Map.copyOf(fieldMappers); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, you could also sort the passThroughMappers in ascending order and simply let mappers with a higher priority override i.e. put without checking for conflicts.
Your solution works just as well so feel free to ignore.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If priority is going to be required, this does feel simpler to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's better to check for conflicts, to avoid cases with inconsistent behavior, depending on the order they show up in the FieldTypeLookup.