diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldType.java b/server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldType.java new file mode 100644 index 0000000000000..dac296b05aa36 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldType.java @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License + * 2.0 and the Server Side Public License, v 1; you may not use this file except + * in compliance with, at your election, the Elastic License 2.0 or the Server + * Side Public License, v 1. + */ + +package org.elasticsearch.index.mapper; + +/** + * Defines a MappedFieldType that exposes dynamic child field types + * + * If the field is named 'my_field', then a user is able to search on + * the field in both of the following ways: + * - Using the field name 'my_field', which will delegate to the field type + * as usual. + * - Using any sub-key, for example 'my_field.some_key'. In this case, the + * search is delegated to {@link #getChildFieldType(String)}, with 'some_key' + * passed as the argument. The field may create a new field type dynamically + * in order to handle the search. + * + * To prevent conflicts between these dynamic sub-keys and multi-fields, any + * field mappers generating field types that implement this interface should + * explicitly disallow multi-fields. + */ +public interface DynamicFieldType { + + /** + * Returns a dynamic MappedFieldType for the given path + */ + MappedFieldType getChildFieldType(String path); + +} diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldMapper.java deleted file mode 100644 index 60cad33afa7b4..0000000000000 --- a/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldMapper.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -package org.elasticsearch.index.mapper; - -import org.elasticsearch.index.analysis.NamedAnalyzer; - -/** - * A field mapper that supports lookup of dynamic sub-keys. If the field mapper is named 'my_field', - * then a user is able to search on the field in both of the following ways: - * - Using the field name 'my_field', which will delegate to the field type - * {@link DynamicKeyFieldMapper#fieldType()} as usual. - * - Using any sub-key, for example 'my_field.some_key'. In this case, the search is delegated - * to {@link DynamicKeyFieldMapper#keyedFieldType(String)}, with 'some_key' passed as the - * argument. The field mapper is allowed to create a new field type dynamically in order - * to handle the search. - * - * To prevent conflicts between these dynamic sub-keys and multi-fields, any field mappers - * implementing this interface should explicitly disallow multi-fields. The constructor makes - * sure to passes an empty multi-fields list to help prevent conflicting sub-keys from being - * registered. - * - * Note: currently 'flattened' fields are the only implementation of this interface. - */ -public abstract class DynamicKeyFieldMapper extends FieldMapper { - - public DynamicKeyFieldMapper(String simpleName, - MappedFieldType defaultFieldType, - NamedAnalyzer indexAnalyzer, - CopyTo copyTo) { - super(simpleName, defaultFieldType, indexAnalyzer, MultiFields.empty(), copyTo); - } - - public abstract MappedFieldType keyedFieldType(String key); - -} diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldTypeLookup.java b/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldTypeLookup.java deleted file mode 100644 index d02773f5e9acc..0000000000000 --- a/server/src/main/java/org/elasticsearch/index/mapper/DynamicKeyFieldTypeLookup.java +++ /dev/null @@ -1,116 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License - * 2.0 and the Server Side Public License, v 1; you may not use this file except - * in compliance with, at your election, the Elastic License 2.0 or the Server - * Side Public License, v 1. - */ - -package org.elasticsearch.index.mapper; - -import java.util.Map; -import java.util.stream.Stream; - -/** - * A container that supports looking up field types for 'dynamic key' fields ({@link DynamicKeyFieldMapper}). - * - * Compared to standard fields, 'dynamic key' fields require special handling. Given a field name of the form - * 'path_to_field.path_to_key', the container will dynamically return a new {@link MappedFieldType} that is - * suitable for performing searches on the sub-key. - * - * Note: we anticipate that 'flattened' fields will be the only implementation {@link DynamicKeyFieldMapper}. - * Flattened object fields live in the 'mapper-flattened' module. - */ -class DynamicKeyFieldTypeLookup { - private final Map mappers; - private final Map aliasToConcreteName; - - /** - * The maximum field depth of any dynamic key mapper. Allows us to stop searching for - * a dynamic key mapper as soon as we've passed the maximum possible field depth. - */ - private final int maxKeyDepth; - - DynamicKeyFieldTypeLookup(Map newMappers, - Map aliasToConcreteName) { - this.mappers = newMappers; - this.aliasToConcreteName = aliasToConcreteName; - this.maxKeyDepth = getMaxKeyDepth(mappers, aliasToConcreteName); - } - - /** - * Check if the given field corresponds to a dynamic key mapper of the - * form 'path_to_field.path_to_key'. If so, returns a field type that - * can be used to perform searches on this field. Otherwise returns null. - */ - MappedFieldType get(String field) { - if (mappers.isEmpty()) { - return null; - } - - int dotIndex = -1; - int fieldDepth = 0; - - while (true) { - if (++fieldDepth > maxKeyDepth) { - return null; - } - - dotIndex = field.indexOf('.', dotIndex + 1); - if (dotIndex < 0) { - return null; - } - - String parentField = field.substring(0, dotIndex); - String concreteField = aliasToConcreteName.getOrDefault(parentField, parentField); - DynamicKeyFieldMapper mapper = mappers.get(concreteField); - - if (mapper != null) { - String key = field.substring(dotIndex + 1); - return mapper.keyedFieldType(key); - } - } - } - - Stream fieldTypes() { - return mappers.values().stream().map(mapper -> mapper.keyedFieldType("")); - } - - // Visible for testing. - static int getMaxKeyDepth(Map dynamicKeyMappers, - Map aliasToConcreteName) { - int maxFieldDepth = 0; - for (Map.Entry entry : aliasToConcreteName.entrySet()) { - String aliasName = entry.getKey(); - String path = entry.getValue(); - if (dynamicKeyMappers.containsKey(path)) { - maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(aliasName)); - } - } - - for (String fieldName : dynamicKeyMappers.keySet()) { - if (dynamicKeyMappers.containsKey(fieldName)) { - maxFieldDepth = Math.max(maxFieldDepth, fieldDepth(fieldName)); - } - } - - return maxFieldDepth; - } - - /** - * Computes the total depth of this field by counting the number of parent fields - * in its path. As an example, the field 'parent1.parent2.field' has depth 3. - */ - private static int fieldDepth(String field) { - int numDots = 0; - int dotIndex = -1; - while (true) { - dotIndex = field.indexOf('.', dotIndex + 1); - if (dotIndex < 0) { - break; - } - numDots++; - } - return numDots + 1; - } -} 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 c590efa1c91d0..92fea82bc6d61 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/FieldTypeLookup.java @@ -15,6 +15,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Set; /** @@ -22,6 +23,7 @@ */ final class FieldTypeLookup { private final Map fullNameToFieldType = new HashMap<>(); + private final Map dynamicFieldTypes = new HashMap<>(); /** * A map from field name to all fields whose content has been copied into it @@ -31,23 +33,22 @@ final class FieldTypeLookup { * For convenience, the set of copied fields includes the field itself. */ private final Map> fieldToCopiedFields = new HashMap<>(); - private final DynamicKeyFieldTypeLookup dynamicKeyLookup; + + private final int maxParentPathDots; FieldTypeLookup( Collection fieldMappers, Collection fieldAliasMappers, Collection runtimeFields ) { - Map dynamicKeyMappers = new HashMap<>(); for (FieldMapper fieldMapper : fieldMappers) { String fieldName = fieldMapper.name(); MappedFieldType fieldType = fieldMapper.fieldType(); fullNameToFieldType.put(fieldType.name(), fieldType); - if (fieldMapper instanceof DynamicKeyFieldMapper) { - dynamicKeyMappers.put(fieldName, (DynamicKeyFieldMapper) fieldMapper); + if (fieldType instanceof DynamicFieldType) { + dynamicFieldTypes.put(fieldType.name(), (DynamicFieldType) fieldType); } - for (String targetField : fieldMapper.copyTo().copyToFields()) { Set sourcePath = fieldToCopiedFields.get(targetField); if (sourcePath == null) { @@ -59,12 +60,20 @@ final class FieldTypeLookup { } } - final Map aliasToConcreteName = new HashMap<>(); + int maxParentPathDots = 0; + for (String dynamicRoot : dynamicFieldTypes.keySet()) { + maxParentPathDots = Math.max(maxParentPathDots, dotCount(dynamicRoot)); + } + this.maxParentPathDots = maxParentPathDots; + for (FieldAliasMapper fieldAliasMapper : fieldAliasMappers) { String aliasName = fieldAliasMapper.name(); String path = fieldAliasMapper.path(); - aliasToConcreteName.put(aliasName, path); - fullNameToFieldType.put(aliasName, fullNameToFieldType.get(path)); + MappedFieldType fieldType = fullNameToFieldType.get(path); + fullNameToFieldType.put(aliasName, fieldType); + if (fieldType instanceof DynamicFieldType) { + dynamicFieldTypes.put(aliasName, (DynamicFieldType) fieldType); + } } for (RuntimeField runtimeField : runtimeFields) { @@ -72,8 +81,16 @@ final class FieldTypeLookup { //this will override concrete fields with runtime fields that have the same name fullNameToFieldType.put(runtimeFieldType.name(), runtimeFieldType); } + } - this.dynamicKeyLookup = new DynamicKeyFieldTypeLookup(dynamicKeyMappers, aliasToConcreteName); + private static int dotCount(String path) { + int dotCount = 0; + for (int i = 0; i < path.length(); i++) { + if (path.charAt(i) == '.') { + dotCount++; + } + } + return dotCount; } /** @@ -84,10 +101,42 @@ MappedFieldType get(String field) { if (fieldType != null) { return fieldType; } + return getDynamicField(field); + } - // If the mapping contains fields that support dynamic sub-key lookup, check - // if this could correspond to a keyed field of the form 'path_to_field.path_to_key'. - return dynamicKeyLookup.get(field); + // for testing + int getMaxParentPathDots() { + return maxParentPathDots; + } + + // Check if the given field corresponds to a dynamic key mapper of the + // form 'path_to_field.path_to_key'. If so, returns a field type that + // can be used to perform searches on this field. Otherwise returns null. + private MappedFieldType getDynamicField(String field) { + if (dynamicFieldTypes.isEmpty()) { + // no parent fields defined + return null; + } + int dotIndex = -1; + int fieldDepth = -1; + + while (true) { + if (++fieldDepth > maxParentPathDots) { + return null; + } + + dotIndex = field.indexOf('.', dotIndex + 1); + if (dotIndex < 0) { + return null; + } + + String parentField = field.substring(0, dotIndex); + DynamicFieldType dft = dynamicFieldTypes.get(parentField); + if (dft != null && Objects.equals(field, parentField) == false) { + String key = field.substring(dotIndex + 1); + return dft.getChildFieldType(key); + } + } } /** @@ -130,7 +179,10 @@ Set sourcePaths(String field) { if (fullNameToFieldType.isEmpty()) { return Set.of(); } - if (dynamicKeyLookup.get(field) != null) { + + // If the field is dynamically generated then return its full path + MappedFieldType fieldType = getDynamicField(field); + if (fieldType != null) { return Set.of(field); } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java index a3493e65656de..5c132b2eaae09 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/flattened/FlattenedFieldMapper.java @@ -31,7 +31,7 @@ import org.elasticsearch.index.fielddata.plain.AbstractLeafOrdinalsFieldData; import org.elasticsearch.index.fielddata.plain.SortedSetOrdinalsIndexFieldData; import org.elasticsearch.index.mapper.ContentPath; -import org.elasticsearch.index.mapper.DynamicKeyFieldMapper; +import org.elasticsearch.index.mapper.DynamicFieldType; import org.elasticsearch.index.mapper.FieldMapper; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.Mapper; @@ -83,7 +83,7 @@ * "key\0some value" and "key2.key3\0true". Note that \0 is used as a reserved separator * character (see {@link FlattenedFieldParser#SEPARATOR}). */ -public final class FlattenedFieldMapper extends DynamicKeyFieldMapper { +public final class FlattenedFieldMapper extends FieldMapper { public static final String CONTENT_TYPE = "flattened"; private static final String KEYED_FIELD_SUFFIX = "._keyed"; @@ -380,7 +380,7 @@ public IndexFieldData build(IndexFieldDataCache cache, CircuitBreakerService * A field type that represents all 'root' values. This field type is used in * searches on the flattened field itself, e.g. 'my_flattened: some_value'. */ - public static final class RootFlattenedFieldType extends StringFieldType { + public static final class RootFlattenedFieldType extends StringFieldType implements DynamicFieldType { private final boolean splitQueriesOnWhitespace; private final boolean eagerGlobalOrdinals; @@ -421,6 +421,11 @@ public IndexFieldData.Builder fielddataBuilder(String fullyQualifiedIndexName, S public ValueFetcher valueFetcher(SearchExecutionContext context, String format) { return SourceValueFetcher.identity(name(), context, format); } + + @Override + public MappedFieldType getChildFieldType(String childPath) { + return new KeyedFlattenedFieldType(name(), childPath, this); + } } private final FlattenedFieldParser fieldParser; @@ -429,7 +434,7 @@ public ValueFetcher valueFetcher(SearchExecutionContext context, String format) private FlattenedFieldMapper(String simpleName, MappedFieldType mappedFieldType, Builder builder) { - super(simpleName, mappedFieldType, Lucene.KEYWORD_ANALYZER, CopyTo.empty()); + super(simpleName, mappedFieldType, Lucene.KEYWORD_ANALYZER, MultiFields.empty(), CopyTo.empty()); this.builder = builder; this.fieldParser = new FlattenedFieldParser(mappedFieldType.name(), mappedFieldType.name() + KEYED_FIELD_SUFFIX, mappedFieldType, builder.depthLimit.get(), builder.ignoreAbove.get(), builder.nullValue.get()); @@ -453,11 +458,6 @@ public RootFlattenedFieldType fieldType() { return (RootFlattenedFieldType) super.fieldType(); } - @Override - public KeyedFlattenedFieldType keyedFieldType(String key) { - return new KeyedFlattenedFieldType(name(), key, fieldType()); - } - @Override protected void parseCreateField(ParseContext context) throws IOException { if (context.parser().currentToken() == XContentParser.Token.VALUE_NULL) { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/FieldTypeLookupTests.java b/server/src/test/java/org/elasticsearch/index/mapper/FieldTypeLookupTests.java index 9f92d75a921dd..a338353b1aef2 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldTypeLookupTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldTypeLookupTests.java @@ -15,9 +15,7 @@ import java.util.Arrays; import java.util.Collection; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Set; import static java.util.Collections.emptyList; @@ -170,14 +168,6 @@ public void testRuntimeFieldsSourcePaths() { } } - private static int size(Iterable iterable) { - int count = 0; - for (MappedFieldType fieldType : iterable) { - count++; - } - return count; - } - public void testFlattenedLookup() { String fieldName = "object1.object2.field"; FlattenedFieldMapper mapper = createFlattenedMapper(fieldName); @@ -189,9 +179,8 @@ public void testFlattenedLookup() { String searchFieldName = fieldName + "." + objectKey; MappedFieldType searchFieldType = lookup.get(searchFieldName); - assertEquals(mapper.keyedFieldType(objectKey).name(), searchFieldType.name()); + assertNotNull(searchFieldType); assertThat(searchFieldType, Matchers.instanceOf(FlattenedFieldMapper.KeyedFlattenedFieldType.class)); - FlattenedFieldMapper.KeyedFlattenedFieldType keyedFieldType = (FlattenedFieldMapper.KeyedFlattenedFieldType) searchFieldType; assertEquals(objectKey, keyedFieldType.key()); } @@ -210,9 +199,8 @@ public void testFlattenedLookupWithAlias() { String searchFieldName = aliasName + "." + objectKey; MappedFieldType searchFieldType = lookup.get(searchFieldName); - assertEquals(mapper.keyedFieldType(objectKey).name(), searchFieldType.name()); + assertNotNull(searchFieldType); assertThat(searchFieldType, Matchers.instanceOf(FlattenedFieldMapper.KeyedFlattenedFieldType.class)); - FlattenedFieldMapper.KeyedFlattenedFieldType keyedFieldType = (FlattenedFieldMapper.KeyedFlattenedFieldType) searchFieldType; assertEquals(objectKey, keyedFieldType.key()); } @@ -236,31 +224,49 @@ public void testFlattenedLookupWithMultipleFields() { assertNotNull(lookup.get(field3 + ".some.key")); } + public void testUnmappedLookupWithDots() { + FieldTypeLookup lookup = new FieldTypeLookup(emptyList(), emptyList(), emptyList()); + assertNull(lookup.get("object.child")); + } + public void testMaxDynamicKeyDepth() { - Map mappers = new HashMap<>(); - Map aliases = new HashMap<>(); - assertEquals(0, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); + { + FieldTypeLookup lookup = new FieldTypeLookup(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + assertEquals(0, lookup.getMaxParentPathDots()); + } // Add a flattened object field. - String name = "object1.object2.field"; - FlattenedFieldMapper flattenedMapper = createFlattenedMapper(name); - mappers.put(name, flattenedMapper); - assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); + { + String name = "object1.object2.field"; + FieldTypeLookup lookup = new FieldTypeLookup( + Collections.singletonList(createFlattenedMapper(name)), + Collections.emptyList(), + Collections.emptyList() + ); + assertEquals(2, lookup.getMaxParentPathDots()); + } // Add a short alias to that field. - String aliasName = "alias"; - aliases.put(aliasName, name); - assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); + { + String name = "object1.object2.field"; + FieldTypeLookup lookup = new FieldTypeLookup( + Collections.singletonList(createFlattenedMapper(name)), + Collections.singletonList(new FieldAliasMapper("alias", "alias", "object1.object2.field")), + Collections.emptyList() + ); + assertEquals(2, lookup.getMaxParentPathDots()); + } // Add a longer alias to that field. - String longAliasName = "object1.object2.object3.alias"; - aliases.put(longAliasName, name); - assertEquals(4, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); - - // Update the long alias to refer to a non-flattened object field. - String fieldName = "field"; - aliases.put(longAliasName, fieldName); - assertEquals(3, DynamicKeyFieldTypeLookup.getMaxKeyDepth(mappers, aliases)); + { + String name = "object1.object2.field"; + FieldTypeLookup lookup = new FieldTypeLookup( + Collections.singletonList(createFlattenedMapper(name)), + Collections.singletonList(new FieldAliasMapper("alias", "object1.object2.object3.alias", "object1.object2.field")), + Collections.emptyList() + ); + assertEquals(2, lookup.getMaxParentPathDots()); + } } private FlattenedFieldMapper createFlattenedMapper(String fieldName) { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/flattened/FlattenedIndexFieldDataTests.java b/server/src/test/java/org/elasticsearch/index/mapper/flattened/FlattenedIndexFieldDataTests.java index 2bd5c67268417..7f2c0c83b6dd0 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/flattened/FlattenedIndexFieldDataTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/flattened/FlattenedIndexFieldDataTests.java @@ -24,8 +24,8 @@ import org.elasticsearch.index.fielddata.IndexFieldDataCache; import org.elasticsearch.index.fielddata.IndexFieldDataService; import org.elasticsearch.index.mapper.ContentPath; +import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.flattened.FlattenedFieldMapper.KeyedFlattenedFieldData; -import org.elasticsearch.index.mapper.flattened.FlattenedFieldMapper.KeyedFlattenedFieldType; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.test.ESSingleNodeTestCase; @@ -46,7 +46,7 @@ public void testGlobalFieldDataCaching() throws IOException { indexService.mapperService()); FlattenedFieldMapper fieldMapper = new FlattenedFieldMapper.Builder("json").build(new ContentPath(1)); - KeyedFlattenedFieldType fieldType1 = fieldMapper.keyedFieldType("key"); + MappedFieldType fieldType1 = fieldMapper.fieldType().getChildFieldType("key"); AtomicInteger onCacheCalled = new AtomicInteger(); ifdService.setListener(new IndexFieldDataCache.Listener() { @@ -83,7 +83,7 @@ public void onCache(ShardId shardId, String fieldName, Accountable ramUsage) { assertEquals(1, onCacheCalled.get()); // Load global field data for the subfield 'other_key'. - KeyedFlattenedFieldType fieldType2 = fieldMapper.keyedFieldType("other_key"); + MappedFieldType fieldType2 = fieldMapper.fieldType().getChildFieldType("other_key"); IndexFieldData ifd2 = ifdService.getForField(fieldType2, "test", () -> { throw new UnsupportedOperationException("search lookup not available"); }); diff --git a/server/src/test/java/org/elasticsearch/search/lookup/LeafDocLookupTests.java b/server/src/test/java/org/elasticsearch/search/lookup/LeafDocLookupTests.java index bb972e0380c07..bbdf762dd1cde 100644 --- a/server/src/test/java/org/elasticsearch/search/lookup/LeafDocLookupTests.java +++ b/server/src/test/java/org/elasticsearch/search/lookup/LeafDocLookupTests.java @@ -11,6 +11,7 @@ import org.elasticsearch.index.fielddata.LeafFieldData; import org.elasticsearch.index.fielddata.ScriptDocValues; import org.elasticsearch.index.mapper.ContentPath; +import org.elasticsearch.index.mapper.DynamicFieldType; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.flattened.FlattenedFieldMapper; import org.elasticsearch.test.ESTestCase; @@ -62,11 +63,12 @@ public void testFlattenedField() { IndexFieldData fieldData2 = createFieldData(docValues2); FlattenedFieldMapper fieldMapper = new FlattenedFieldMapper.Builder("field").build(new ContentPath(1)); - FlattenedFieldMapper.KeyedFlattenedFieldType fieldType1 = fieldMapper.keyedFieldType("key1"); - FlattenedFieldMapper.KeyedFlattenedFieldType fieldType2 = fieldMapper.keyedFieldType("key2"); + DynamicFieldType fieldType = fieldMapper.fieldType(); + MappedFieldType fieldType1 = fieldType.getChildFieldType("key1"); + MappedFieldType fieldType2 = fieldType.getChildFieldType("key2"); - Function> fieldDataSupplier = fieldType -> { - FlattenedFieldMapper.KeyedFlattenedFieldType keyedFieldType = (FlattenedFieldMapper.KeyedFlattenedFieldType) fieldType; + Function> fieldDataSupplier = ft -> { + FlattenedFieldMapper.KeyedFlattenedFieldType keyedFieldType = (FlattenedFieldMapper.KeyedFlattenedFieldType) ft; return keyedFieldType.key().equals("key1") ? fieldData1 : fieldData2; };