diff --git a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/TSDBPassthroughIndexingIT.java b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/TSDBPassthroughIndexingIT.java index aa3fa2a730be3..b8d7d18dec475 100644 --- a/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/TSDBPassthroughIndexingIT.java +++ b/modules/data-streams/src/internalClusterTest/java/org/elasticsearch/datastreams/TSDBPassthroughIndexingIT.java @@ -17,6 +17,8 @@ import org.elasticsearch.action.admin.indices.template.put.TransportPutComposableIndexTemplateAction; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.delete.DeleteRequest; +import org.elasticsearch.action.fieldcaps.FieldCapabilitiesRequest; +import org.elasticsearch.action.fieldcaps.FieldCapabilitiesResponse; import org.elasticsearch.action.get.GetRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.search.SearchRequest; @@ -70,6 +72,7 @@ public class TSDBPassthroughIndexingIT extends ESSingleNodeTestCase { }, "attributes": { "type": "passthrough", + "priority": 0, "dynamic": true, "time_series_dimension": true }, @@ -197,31 +200,20 @@ public void testIndexingGettingAndSearching() throws Exception { assertMap(attributes.get("pod.ip"), matchesMap().entry("type", "ip").entry("time_series_dimension", true)); assertMap(attributes.get("pod.uid"), matchesMap().entry("type", "keyword").entry("time_series_dimension", true)); assertMap(attributes.get("pod.name"), matchesMap().entry("type", "keyword").entry("time_series_dimension", true)); - // alias field mappers: - assertMap( - ObjectPath.eval("properties.metricset", mapping), - matchesMap().entry("type", "alias").entry("path", "attributes.metricset") - ); - assertMap( - ObjectPath.eval("properties.number.properties.long", mapping), - matchesMap().entry("type", "alias").entry("path", "attributes.number.long") - ); - assertMap( - ObjectPath.eval("properties.number.properties.double", mapping), - matchesMap().entry("type", "alias").entry("path", "attributes.number.double") - ); - assertMap( - ObjectPath.eval("properties.pod.properties", mapping), - matchesMap().extraOk().entry("name", matchesMap().entry("type", "alias").entry("path", "attributes.pod.name")) - ); - assertMap( - ObjectPath.eval("properties.pod.properties", mapping), - matchesMap().extraOk().entry("uid", matchesMap().entry("type", "alias").entry("path", "attributes.pod.uid")) - ); - assertMap( - ObjectPath.eval("properties.pod.properties", mapping), - matchesMap().extraOk().entry("ip", matchesMap().entry("type", "alias").entry("path", "attributes.pod.ip")) - ); + + FieldCapabilitiesResponse fieldCaps = client().fieldCaps(new FieldCapabilitiesRequest().fields("*").indices("k8s")).actionGet(); + assertTrue(fieldCaps.getField("attributes.metricset").get("keyword").isDimension()); + assertTrue(fieldCaps.getField("metricset").get("keyword").isDimension()); + assertTrue(fieldCaps.getField("attributes.number.long").get("long").isDimension()); + assertTrue(fieldCaps.getField("number.long").get("long").isDimension()); + assertTrue(fieldCaps.getField("attributes.number.double").get("float").isDimension()); + assertTrue(fieldCaps.getField("number.double").get("float").isDimension()); + assertTrue(fieldCaps.getField("attributes.pod.ip").get("ip").isDimension()); + assertTrue(fieldCaps.getField("pod.ip").get("ip").isDimension()); + assertTrue(fieldCaps.getField("attributes.pod.uid").get("keyword").isDimension()); + assertTrue(fieldCaps.getField("pod.uid").get("keyword").isDimension()); + assertTrue(fieldCaps.getField("attributes.pod.name").get("keyword").isDimension()); + assertTrue(fieldCaps.getField("pod.name").get("keyword").isDimension()); } public void testIndexingGettingAndSearchingShrunkIndex() throws Exception { diff --git a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java index ccb8abbb9efab..8ca5fe1fcdbcf 100644 --- a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java +++ b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamGetWriteIndexTests.java @@ -240,7 +240,7 @@ public void setup() throws Exception { new MetadataFieldMapper[] { dtfm }, Collections.emptyMap() ); - MappingLookup mappingLookup = MappingLookup.fromMappers(mapping, List.of(dtfm, dateFieldMapper), List.of(), List.of()); + MappingLookup mappingLookup = MappingLookup.fromMappers(mapping, List.of(dtfm, dateFieldMapper), List.of()); indicesService = DataStreamTestHelper.mockIndicesServices(mappingLookup); } diff --git a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamIndexSettingsProviderTests.java b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamIndexSettingsProviderTests.java index 5933b5caba001..85f0d354576a4 100644 --- a/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamIndexSettingsProviderTests.java +++ b/modules/data-streams/src/test/java/org/elasticsearch/datastreams/DataStreamIndexSettingsProviderTests.java @@ -631,10 +631,12 @@ public void testGenerateRoutingPathFromPassThroughObject() throws Exception { "properties": { "labels": { "type": "passthrough", - "time_series_dimension": true + "time_series_dimension": true, + "priority": 2 }, "metrics": { - "type": "passthrough" + "type": "passthrough", + "priority": 1 }, "another_field": { "type": "keyword" diff --git a/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/150_tsdb.yml b/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/150_tsdb.yml index 355a549b6fbf1..a1ded40ce1852 100644 --- a/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/150_tsdb.yml +++ b/modules/data-streams/src/yamlRestTest/resources/rest-api-spec/test/data_stream/150_tsdb.yml @@ -195,8 +195,8 @@ index without timestamp with pipeline: --- dynamic templates: - requires: - cluster_features: ["gte_v8.13.0"] - reason: "Support for dynamic fields was added in 8.13" + cluster_features: ["mapper.pass_through_priority"] + reason: support for priority in passthrough objects - do: allowed_warnings: - "index template [my-dynamic-template] has index patterns [k9s*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-dynamic-template] will take precedence during new index creation" @@ -219,6 +219,7 @@ dynamic templates: type: passthrough dynamic: true time_series_dimension: true + priority: 0 dynamic_templates: - counter_metric: mapping: @@ -326,8 +327,8 @@ dynamic templates: --- dynamic templates - conflicting aliases: - requires: - cluster_features: ["gte_v8.13.0"] - reason: "Support for dynamic fields was added in 8.13" + cluster_features: ["mapper.pass_through_priority"] + reason: support for priority in passthrough objects - do: allowed_warnings: - "index template [my-dynamic-template] has index patterns [k9s*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-dynamic-template] will take precedence during new index creation" @@ -350,10 +351,12 @@ dynamic templates - conflicting aliases: type: passthrough dynamic: true time_series_dimension: true + priority: 2 resource_attributes: type: passthrough dynamic: true time_series_dimension: true + priority: 1 dynamic_templates: - counter_metric: mapping: @@ -391,7 +394,7 @@ dynamic templates - conflicting aliases: filterA: filter: term: - dim: "C" + dim: A aggs: tsids: terms: @@ -410,7 +413,7 @@ dynamic templates - conflicting aliases: filterA: filter: term: - attributes.dim: A + resource_attributes.dim: C aggs: tsids: terms: @@ -423,8 +426,8 @@ dynamic templates - conflicting aliases: --- dynamic templates with nesting: - requires: - cluster_features: ["gte_v8.13.0"] - reason: "Support for dynamic fields was added in 8.13" + cluster_features: ["mapper.pass_through_priority"] + reason: support for priority in passthrough objects - do: allowed_warnings: - "index template [my-dynamic-template] has index patterns [k9s*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-dynamic-template] will take precedence during new index creation" @@ -447,6 +450,7 @@ dynamic templates with nesting: type: passthrough dynamic: true time_series_dimension: true + priority: 2 resource: type: object properties: @@ -454,6 +458,7 @@ dynamic templates with nesting: type: passthrough dynamic: true time_series_dimension: true + priority: 1 dynamic_templates: - counter_metric: mapping: @@ -580,8 +585,9 @@ dynamic templates with nesting: --- dynamic templates with incremental indexing: - requires: - cluster_features: ["gte_v8.13.0"] - reason: "Support for dynamic fields was added in 8.13" + cluster_features: ["mapper.pass_through_priority"] + reason: support for priority in passthrough objects + - do: allowed_warnings: - "index template [my-dynamic-template] has index patterns [k9s*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-dynamic-template] will take precedence during new index creation" @@ -604,6 +610,7 @@ dynamic templates with incremental indexing: type: passthrough dynamic: true time_series_dimension: true + priority: 2 resource: type: object properties: @@ -611,6 +618,7 @@ dynamic templates with incremental indexing: type: passthrough dynamic: true time_series_dimension: true + priority: 1 dynamic_templates: - counter_metric: mapping: @@ -774,8 +782,8 @@ dynamic templates with incremental indexing: --- subobject in passthrough object auto flatten: - requires: - cluster_features: ["gte_v8.13.0"] - reason: "Support for passthrough fields was added in 8.13" + cluster_features: ["mapper.pass_through_priority"] + reason: support for priority in passthrough objects - do: allowed_warnings: - "index template [my-passthrough-template] has index patterns [k9s*] matching patterns from existing older templates [global] with patterns (global => [*]); this template [my-passthrough-template] will take precedence during new index creation" @@ -794,6 +802,7 @@ subobject in passthrough object auto flatten: attributes: type: passthrough time_series_dimension: true + priority: 0 properties: subcategory: type: object @@ -833,11 +842,36 @@ enable subobjects in passthrough object: index: number_of_shards: 1 mode: time_series - time_series: - start_time: 2023-08-31T13:03:08.138Z mappings: properties: attributes: type: passthrough subobjects: true + +--- +passthrough objects with duplicate priority: + - requires: + cluster_features: ["mapper.pass_through_priority"] + reason: support for priority in passthrough objects + - do: + catch: /has a conflicting param/ + indices.put_index_template: + name: my-dynamic-template + body: + index_patterns: [k9s*] + data_stream: {} + template: + settings: + index: + number_of_shards: 1 + mode: time_series + + mappings: + properties: + attributes: + type: passthrough + priority: 1 + resource.attributes: + type: passthrough + priority: 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 5e3dbe9590b99..7070c387fbb97 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; @@ -38,9 +39,14 @@ final class FieldTypeLookup { private final int maxParentPathDots; + FieldTypeLookup(Collection fieldMappers, Collection fieldAliasMappers) { + this(fieldMappers, fieldAliasMappers, List.of(), List.of()); + } + FieldTypeLookup( Collection fieldMappers, Collection fieldAliasMappers, + Collection passThroughMappers, Collection 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 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) { + if (conflict.priority() > passThroughMapper.priority()) { + // Keep the conflicting field if it has higher priority. + 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); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java index ef9a200a3aea9..230074f37b127 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MapperFeatures.java @@ -19,6 +19,10 @@ public class MapperFeatures implements FeatureSpecification { @Override public Set getFeatures() { - return Set.of(IgnoredSourceFieldMapper.TRACK_IGNORED_SOURCE, RangeFieldMapper.NULL_VALUES_OFF_BY_ONE_FIX); + return Set.of( + IgnoredSourceFieldMapper.TRACK_IGNORED_SOURCE, + PassThroughObjectMapper.PASS_THROUGH_PRIORITY, + RangeFieldMapper.NULL_VALUES_OFF_BY_ONE_FIX + ); } } 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 42b6f9bfefd5a..83e6984285749 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/MappingLookup.java @@ -42,7 +42,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(); @@ -70,24 +70,29 @@ public static MappingLookup fromMapping(Mapping mapping) { List newObjectMappers = new ArrayList<>(); List newFieldMappers = new ArrayList<>(); List newFieldAliasMappers = new ArrayList<>(); + List 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 objectMappers, Collection fieldMappers, - Collection fieldAliasMappers + Collection fieldAliasMappers, + Collection passThroughMappers ) { - if (mapper instanceof ObjectMapper objectMapper) { + 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); @@ -98,7 +103,7 @@ private static void collect( } for (Mapper child : mapper) { - collect(child, objectMappers, fieldMappers, fieldAliasMappers); + collect(child, objectMappers, fieldMappers, fieldAliasMappers, passThroughMappers); } } @@ -114,22 +119,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 mappers, Collection objectMappers, - Collection aliasMappers + Collection aliasMappers, + Collection passThroughMappers ) { - return new MappingLookup(mapping, mappers, objectMappers, aliasMappers); + return new MappingLookup(mapping, mappers, objectMappers, aliasMappers, passThroughMappers); + } + + public static MappingLookup fromMappers(Mapping mapping, Collection mappers, Collection objectMappers) { + return new MappingLookup(mapping, mappers, objectMappers, List.of(), List.of()); } private MappingLookup( Mapping mapping, Collection mappers, Collection objectMappers, - Collection aliasMappers + Collection aliasMappers, + Collection passThroughMappers ) { this.totalFieldsCount = mapping.getRoot().getTotalFieldsCount(); this.mapping = mapping; @@ -175,8 +187,9 @@ private MappingLookup( } } + PassThroughObjectMapper.checkForDuplicatePriorities(passThroughMappers); final Collection runtimeFields = mapping.getRoot().runtimeFields(); - this.fieldTypeLookup = new FieldTypeLookup(mappers, aliasMappers, runtimeFields); + this.fieldTypeLookup = new FieldTypeLookup(mappers, aliasMappers, passThroughMappers, runtimeFields); Map inferenceFields = new HashMap<>(); for (FieldMapper mapper : mappers) { @@ -190,7 +203,7 @@ private MappingLookup( // 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); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/PassThroughObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/PassThroughObjectMapper.java index 668237571984a..df3c6c54b27fd 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/PassThroughObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/PassThroughObjectMapper.java @@ -9,14 +9,18 @@ package org.elasticsearch.index.mapper; import org.elasticsearch.common.Explicit; +import org.elasticsearch.features.NodeFeature; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.xcontent.XContentBuilder; import java.io.IOException; +import java.util.Collection; +import java.util.HashMap; import java.util.Locale; import java.util.Map; import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeBooleanValue; +import static org.elasticsearch.common.xcontent.support.XContentMapValues.nodeIntegerValue; /** * Mapper for pass-through objects. @@ -24,15 +28,28 @@ * Pass-through objects allow creating fields inside them that can also be referenced directly in search queries. * They also include parameters that affect how nested fields get configured. For instance, if parameter "time_series_dimension" * is set, eligible subfields are marked as dimensions and keyword fields are additionally included in routing and tsid calculations. + * + * In case different pass-through objects contain subfields with the same name (excluding the pass-through prefix), their aliases conflict. + * To resolve this, the pass-through spec specifies which object takes precedence through required parameter "priority"; non-negative + * integer values are accepted, with the highest priority value winning in case of conflicting aliases. + * + * Note that this is an experimental, undocumented mapper type, currently intended for prototyping purposes only. + * It has not been vetted for use in production systems. */ public class PassThroughObjectMapper extends ObjectMapper { public static final String CONTENT_TYPE = "passthrough"; + public static final String PRIORITY_PARAM_NAME = "priority"; + + static final NodeFeature PASS_THROUGH_PRIORITY = new NodeFeature("mapper.pass_through_priority"); public static class Builder extends ObjectMapper.Builder { // Controls whether subfields are configured as time-series dimensions. protected Explicit timeSeriesDimensionSubFields = Explicit.IMPLICIT_FALSE; + // Controls which pass-through fields take precedence in case of conflicting aliases. + protected int priority = -1; + public Builder(String name) { // Subobjects are not currently supported. super(name, Explicit.IMPLICIT_FALSE); @@ -52,6 +69,11 @@ public PassThroughObjectMapper.Builder setContainsDimensions() { return this; } + public PassThroughObjectMapper.Builder setPriority(int priority) { + this.priority = priority; + return this; + } + @Override public PassThroughObjectMapper build(MapperBuilderContext context) { return new PassThroughObjectMapper( @@ -60,7 +82,8 @@ public PassThroughObjectMapper build(MapperBuilderContext context) { enabled, dynamic, buildMappers(context.createChildContext(name(), timeSeriesDimensionSubFields.value(), dynamic)), - timeSeriesDimensionSubFields + timeSeriesDimensionSubFields, + priority ); } } @@ -68,34 +91,51 @@ public PassThroughObjectMapper build(MapperBuilderContext context) { // If set, its subfields are marked as time-series dimensions (for the types supporting this). private final Explicit timeSeriesDimensionSubFields; + private final int priority; + PassThroughObjectMapper( String name, String fullPath, Explicit enabled, Dynamic dynamic, Map mappers, - Explicit timeSeriesDimensionSubFields + Explicit timeSeriesDimensionSubFields, + int priority ) { // Subobjects are not currently supported. super(name, fullPath, enabled, Explicit.IMPLICIT_FALSE, Explicit.IMPLICIT_FALSE, dynamic, mappers); this.timeSeriesDimensionSubFields = timeSeriesDimensionSubFields; + this.priority = priority; + if (priority < 0) { + throw new MapperException("Pass-through object [" + fullPath + "] is missing a non-negative value for parameter [priority]"); + } } @Override PassThroughObjectMapper withoutMappers() { - return new PassThroughObjectMapper(simpleName(), fullPath(), enabled, dynamic, Map.of(), timeSeriesDimensionSubFields); + return new PassThroughObjectMapper(simpleName(), fullPath(), enabled, dynamic, Map.of(), timeSeriesDimensionSubFields, priority); + } + + @Override + public String typeName() { + return CONTENT_TYPE; } public boolean containsDimensions() { return timeSeriesDimensionSubFields.value(); } + public int priority() { + return priority; + } + @Override public PassThroughObjectMapper.Builder newBuilder(IndexVersion indexVersionCreated) { PassThroughObjectMapper.Builder builder = new PassThroughObjectMapper.Builder(simpleName()); builder.enabled = enabled; builder.dynamic = dynamic; builder.timeSeriesDimensionSubFields = timeSeriesDimensionSubFields; + builder.priority = priority; return builder; } @@ -118,7 +158,8 @@ public PassThroughObjectMapper merge(Mapper mergeWith, MapperMergeContext parent mergeResult.enabled(), mergeResult.dynamic(), mergeResult.mappers(), - containsDimensions + containsDimensions, + Math.max(priority, mergeWithObject.priority) ); } @@ -129,6 +170,9 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws if (timeSeriesDimensionSubFields.explicit()) { builder.field(TimeSeriesParams.TIME_SERIES_DIMENSION_PARAM, timeSeriesDimensionSubFields.value()); } + if (priority >= 0) { + builder.field(PRIORITY_PARAM_NAME, priority); + } if (dynamic != null) { builder.field("dynamic", dynamic.name().toLowerCase(Locale.ROOT)); } @@ -157,6 +201,33 @@ protected static void parsePassthrough(String name, Map node, Pa ); node.remove(TimeSeriesParams.TIME_SERIES_DIMENSION_PARAM); } + fieldNode = node.get(PRIORITY_PARAM_NAME); + if (fieldNode != null) { + builder.priority = nodeIntegerValue(fieldNode); + node.remove(PRIORITY_PARAM_NAME); + } + } + } + + /** + * Checks the passed objects for duplicate or negative priorities. + * @param passThroughMappers objects to check + */ + public static void checkForDuplicatePriorities(Collection passThroughMappers) { + Map seen = new HashMap<>(); + for (PassThroughObjectMapper mapper : passThroughMappers) { + String conflict = seen.put(mapper.priority, mapper.name()); + if (conflict != null) { + throw new MapperException( + "Pass-through object [" + + mapper.name() + + "] has a conflicting param [priority=" + + mapper.priority + + "] with object [" + + conflict + + "]" + ); + } } } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java index c19809760ec43..2d4a9fd639ab5 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java @@ -18,8 +18,6 @@ import org.elasticsearch.index.IndexVersions; import org.elasticsearch.index.mapper.DynamicTemplate.XContentFieldType; import org.elasticsearch.index.mapper.MapperService.MergeReason; -import org.elasticsearch.logging.LogManager; -import org.elasticsearch.logging.Logger; import org.elasticsearch.xcontent.ToXContent; import org.elasticsearch.xcontent.XContentBuilder; @@ -77,8 +75,6 @@ public static class Builder extends ObjectMapper.Builder { protected Explicit dateDetection = Defaults.DATE_DETECTION; protected Explicit numericDetection = Defaults.NUMERIC_DETECTION; - private static final Logger logger = LogManager.getLogger(RootObjectMapper.Builder.class); - public Builder(String name, Explicit subobjects) { super(name, subobjects); } @@ -111,15 +107,13 @@ public RootObjectMapper.Builder addRuntimeFields(Map runti @Override public RootObjectMapper build(MapperBuilderContext context) { - Map mappers = buildMappers(context.createChildContext(null, dynamic)); - mappers.putAll(getAliasMappers(mappers, context)); return new RootObjectMapper( name(), enabled, subobjects, trackArraySource, dynamic, - mappers, + buildMappers(context.createChildContext(null, dynamic)), new HashMap<>(runtimeFields), dynamicDateTimeFormatters, dynamicTemplates, @@ -127,130 +121,6 @@ public RootObjectMapper build(MapperBuilderContext context) { numericDetection ); } - - Map getAliasMappers(Map mappers, MapperBuilderContext context) { - Map newMappers = new HashMap<>(); - Map objectIntermediates = new HashMap<>(1); - Map objectIntermediatesFullName = new HashMap<>(1); - getAliasMappers(mappers, mappers, newMappers, objectIntermediates, objectIntermediatesFullName, context, 0); - for (var entry : objectIntermediates.entrySet()) { - newMappers.put(entry.getKey(), entry.getValue().build(context)); - } - return newMappers; - } - - void getAliasMappers( - Map mappers, - Map topLevelMappers, - Map aliasMappers, - Map objectIntermediates, - Map objectIntermediatesFullName, - MapperBuilderContext context, - int level - ) { - if (level >= MAX_NESTING_LEVEL_FOR_PASS_THROUGH_OBJECTS) { - logger.warn("Exceeded maximum nesting level for searching for pass-through object fields within object fields."); - return; - } - for (Mapper mapper : mappers.values()) { - // Create aliases for all fields in child passthrough mappers and place them under the root object. - if (mapper instanceof PassThroughObjectMapper passthroughMapper) { - for (Mapper internalMapper : passthroughMapper.mappers.values()) { - if (internalMapper instanceof FieldMapper fieldMapper) { - // If there's a conflicting alias with the same name at the root level, we don't want to throw an error - // to avoid indexing disruption. - // TODO: record an error without affecting document indexing, so that it can be investigated later. - Mapper conflict = mappers.get(fieldMapper.simpleName()); - if (conflict != null) { - if (conflict.typeName().equals(FieldAliasMapper.CONTENT_TYPE) == false - || ((FieldAliasMapper) conflict).path().equals(fieldMapper.mappedFieldType.name()) == false) { - logger.warn( - "Root alias for field " - + fieldMapper.name() - + " conflicts with existing field or alias, skipping alias creation." - ); - } - } else { - // Check if the field name contains dots, as aliases require nesting within objects in this case. - String[] fieldNameParts = fieldMapper.simpleName().split("\\."); - if (fieldNameParts.length == 0) { - throw new IllegalArgumentException("field name cannot contain only dots"); - } - if (fieldNameParts.length == 1) { - // No nesting required, add the alias directly to the root. - FieldAliasMapper aliasMapper = new FieldAliasMapper.Builder(fieldMapper.simpleName()).path( - fieldMapper.mappedFieldType.name() - ).build(context); - aliasMappers.put(aliasMapper.simpleName(), aliasMapper); - } else { - conflict = topLevelMappers.get(fieldNameParts[0]); - if (conflict != null) { - if (isConflictingObject(conflict, fieldNameParts)) { - throw new IllegalArgumentException( - "Conflicting objects created during alias generation for pass-through field: [" - + conflict.name() - + "]" - ); - } - } - - // Nest the alias within object(s). - String realFieldName = fieldNameParts[fieldNameParts.length - 1]; - Mapper.Builder fieldBuilder = new FieldAliasMapper.Builder(realFieldName).path( - fieldMapper.mappedFieldType.name() - ); - ObjectMapper.Builder intermediate = null; - for (int i = fieldNameParts.length - 2; i >= 0; --i) { - String intermediateObjectName = fieldNameParts[i]; - intermediate = objectIntermediatesFullName.computeIfAbsent( - concatStrings(fieldNameParts, i), - s -> new ObjectMapper.Builder(intermediateObjectName, ObjectMapper.Defaults.SUBOBJECTS) - ); - intermediate.add(fieldBuilder); - fieldBuilder = intermediate; - } - objectIntermediates.putIfAbsent(fieldNameParts[0], intermediate); - } - } - } - } - } else if (mapper instanceof ObjectMapper objectMapper) { - // Call recursively to check child fields. The level guards against long recursive call sequences. - getAliasMappers( - objectMapper.mappers, - topLevelMappers, - aliasMappers, - objectIntermediates, - objectIntermediatesFullName, - context, - level + 1 - ); - } - } - } - } - - private static String concatStrings(String[] parts, int last) { - StringBuilder builder = new StringBuilder(); - for (int i = 0; i <= last; i++) { - builder.append('.'); - builder.append(parts[i]); - } - return builder.toString(); - } - - private static boolean isConflictingObject(Mapper mapper, String[] parts) { - for (int i = 0; i < parts.length - 1; i++) { - if (mapper == null) { - return true; - } - if (mapper instanceof ObjectMapper objectMapper) { - mapper = objectMapper.getMapper(parts[i + 1]); - } else { - return true; - } - } - return mapper == null; } private final Explicit dynamicDateTimeFormatters; diff --git a/server/src/test/java/org/elasticsearch/index/mapper/DynamicFieldsBuilderTests.java b/server/src/test/java/org/elasticsearch/index/mapper/DynamicFieldsBuilderTests.java index bab046d41b6e5..e8a8535017889 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/DynamicFieldsBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/DynamicFieldsBuilderTests.java @@ -70,7 +70,7 @@ public void testCreateDynamicStringFieldAsKeywordForDimension() throws IOExcepti SourceFieldMapper sourceMapper = new SourceFieldMapper.Builder(null, Settings.EMPTY, false).setSynthetic().build(); RootObjectMapper root = new RootObjectMapper.Builder("_doc", Explicit.IMPLICIT_TRUE).add( - new PassThroughObjectMapper.Builder("labels").setContainsDimensions().dynamic(ObjectMapper.Dynamic.TRUE) + new PassThroughObjectMapper.Builder("labels").setPriority(0).setContainsDimensions().dynamic(ObjectMapper.Dynamic.TRUE) ).build(MapperBuilderContext.root(false, false)); Mapping mapping = new Mapping(root, new MetadataFieldMapper[] { sourceMapper }, Map.of()); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java b/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java index c02df8336a66d..8f6565cc5da94 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java @@ -193,6 +193,6 @@ private static MappingLookup createMappingLookup( new MetadataFieldMapper[0], Collections.emptyMap() ); - return MappingLookup.fromMappers(mapping, fieldMappers, objectMappers, fieldAliasMappers); + return MappingLookup.fromMappers(mapping, fieldMappers, objectMappers, fieldAliasMappers, emptyList()); } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/FieldNamesFieldTypeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/FieldNamesFieldTypeTests.java index 9417bd924e221..31736d7ff9b0f 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldNamesFieldTypeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldNamesFieldTypeTests.java @@ -35,7 +35,7 @@ public void testTermQuery() { settings ); List mappers = Stream.of(fieldNamesFieldType, fieldType).map(MockFieldMapper::new).toList(); - MappingLookup mappingLookup = MappingLookup.fromMappers(Mapping.EMPTY, mappers, emptyList(), emptyList()); + MappingLookup mappingLookup = MappingLookup.fromMappers(Mapping.EMPTY, mappers, emptyList()); SearchExecutionContext searchExecutionContext = SearchExecutionContextHelper.createSimple(indexSettings, null, null); Query termQuery = fieldNamesFieldType.termQuery("field_name", searchExecutionContext); assertEquals(new TermQuery(new Term(FieldNamesFieldMapper.CONTENT_TYPE, "field_name")), termQuery); 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 3f50b9fdf6621..04013bf01d57c 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldTypeLookupTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldTypeLookupTests.java @@ -8,6 +8,7 @@ package org.elasticsearch.index.mapper; +import org.elasticsearch.common.Explicit; import org.elasticsearch.index.mapper.flattened.FlattenedFieldMapper; import org.elasticsearch.test.ESTestCase; import org.hamcrest.Matchers; @@ -16,6 +17,7 @@ import java.util.Collection; import java.util.Collections; import java.util.List; +import java.util.Map; import java.util.Set; import static java.util.Collections.emptyList; @@ -30,7 +32,7 @@ public class FieldTypeLookupTests extends ESTestCase { public void testEmpty() { - FieldTypeLookup lookup = new FieldTypeLookup(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(emptyList(), emptyList()); assertNull(lookup.get("foo")); Collection names = lookup.getMatchingFieldNames("foo"); assertNotNull(names); @@ -39,7 +41,7 @@ public void testEmpty() { public void testAddNewField() { MockFieldMapper f = new MockFieldMapper("foo"); - FieldTypeLookup lookup = new FieldTypeLookup(Collections.singletonList(f), emptyList(), Collections.emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(Collections.singletonList(f), emptyList()); assertNull(lookup.get("bar")); assertEquals(f.fieldType(), lookup.get("foo")); } @@ -48,11 +50,7 @@ public void testAddFieldAlias() { MockFieldMapper field = new MockFieldMapper("foo"); FieldAliasMapper alias = new FieldAliasMapper("alias", "alias", "foo"); - FieldTypeLookup lookup = new FieldTypeLookup( - Collections.singletonList(field), - Collections.singletonList(alias), - Collections.emptyList() - ); + FieldTypeLookup lookup = new FieldTypeLookup(Collections.singletonList(field), Collections.singletonList(alias)); MappedFieldType aliasType = lookup.get("alias"); assertEquals(field.fieldType(), aliasType); @@ -79,6 +77,7 @@ public void testGetMatchingFieldNames() { FieldTypeLookup lookup = new FieldTypeLookup( List.of(field1, field2, field3, flattened), List.of(alias1, alias2), + List.of(), List.of(runtimeField, multi) ); @@ -124,7 +123,7 @@ public void testSourcePathWithMultiFields() { // Adding a subfield that is not multi-field MockFieldMapper subfield = new MockFieldMapper.Builder("field.subfield4").build(MapperBuilderContext.root(false, false)); - FieldTypeLookup lookup = new FieldTypeLookup(List.of(field, subfield), emptyList(), emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(List.of(field, subfield), emptyList()); assertEquals(Set.of("field"), lookup.sourcePaths("field")); assertEquals(Set.of("field"), lookup.sourcePaths("field.subfield1")); @@ -148,11 +147,7 @@ public void testSourcePathsWithCopyTo() { .copyTo("field.nested") .build(MapperBuilderContext.root(false, false)); - FieldTypeLookup lookup = new FieldTypeLookup( - Arrays.asList(field, nestedField, otherField, otherNestedField), - emptyList(), - emptyList() - ); + FieldTypeLookup lookup = new FieldTypeLookup(Arrays.asList(field, nestedField, otherField, otherNestedField), emptyList()); assertEquals(Set.of("other_field", "other_field.nested", "field"), lookup.sourcePaths("field")); assertEquals(Set.of("other_field", "other_field.nested", "field"), lookup.sourcePaths("field.subfield1")); @@ -172,7 +167,12 @@ public void testRuntimeFieldsLookup() { ) ); - FieldTypeLookup fieldTypeLookup = new FieldTypeLookup(List.of(concrete), emptyList(), List.of(runtime, runtimeLong, multi)); + FieldTypeLookup fieldTypeLookup = new FieldTypeLookup( + List.of(concrete), + emptyList(), + emptyList(), + List.of(runtime, runtimeLong, multi) + ); assertThat(fieldTypeLookup.get("concrete"), instanceOf(MockFieldMapper.FakeFieldType.class)); assertThat(fieldTypeLookup.get("string"), instanceOf(TestRuntimeField.TestRuntimeFieldType.class)); assertThat(fieldTypeLookup.get("string").typeName(), equalTo("type")); @@ -202,6 +202,7 @@ public void testRuntimeFieldsOverrideConcreteFields() { FieldTypeLookup fieldTypeLookup = new FieldTypeLookup( List.of(field, concrete, subfield, flattened), emptyList(), + emptyList(), List.of(fieldOverride, runtime, subfieldOverride, flattenedRuntime) ); assertThat(fieldTypeLookup.get("field"), instanceOf(TestRuntimeField.TestRuntimeFieldType.class)); @@ -223,7 +224,12 @@ public void testRuntimeFieldsSourcePaths() { TestRuntimeField field2 = new TestRuntimeField("field2", "type"); TestRuntimeField subfield = new TestRuntimeField("object.subfield", "type"); - FieldTypeLookup fieldTypeLookup = new FieldTypeLookup(List.of(field1, concrete), emptyList(), List.of(field2, subfield)); + FieldTypeLookup fieldTypeLookup = new FieldTypeLookup( + List.of(field1, concrete), + emptyList(), + emptyList(), + List.of(field2, subfield) + ); { Set sourcePaths = fieldTypeLookup.sourcePaths("field1"); assertEquals(1, sourcePaths.size()); @@ -245,7 +251,7 @@ public void testFlattenedLookup() { String fieldName = "object1.object2.field"; FlattenedFieldMapper mapper = createFlattenedMapper(fieldName); - FieldTypeLookup lookup = new FieldTypeLookup(singletonList(mapper), emptyList(), emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(singletonList(mapper), emptyList()); assertEquals(mapper.fieldType(), lookup.get(fieldName)); String objectKey = "key1.key2"; @@ -271,7 +277,7 @@ public void testFlattenedLookupWithAlias() { String aliasName = "alias"; FieldAliasMapper alias = new FieldAliasMapper(aliasName, aliasName, fieldName); - FieldTypeLookup lookup = new FieldTypeLookup(singletonList(mapper), singletonList(alias), emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(singletonList(mapper), singletonList(alias), emptyList(), emptyList()); assertEquals(mapper.fieldType(), lookup.get(aliasName)); String objectKey = "key1.key2"; @@ -293,35 +299,31 @@ public void testFlattenedLookupWithMultipleFields() { FlattenedFieldMapper mapper2 = createFlattenedMapper(field2); FlattenedFieldMapper mapper3 = createFlattenedMapper(field3); - FieldTypeLookup lookup = new FieldTypeLookup(Arrays.asList(mapper1, mapper2), emptyList(), emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(Arrays.asList(mapper1, mapper2), emptyList()); assertNotNull(lookup.get(field1 + ".some.key")); assertNotNull(lookup.get(field2 + ".some.key")); - lookup = new FieldTypeLookup(Arrays.asList(mapper1, mapper2, mapper3), emptyList(), emptyList()); + lookup = new FieldTypeLookup(Arrays.asList(mapper1, mapper2, mapper3), emptyList()); assertNotNull(lookup.get(field1 + ".some.key")); assertNotNull(lookup.get(field2 + ".some.key")); assertNotNull(lookup.get(field3 + ".some.key")); } public void testUnmappedLookupWithDots() { - FieldTypeLookup lookup = new FieldTypeLookup(emptyList(), emptyList(), emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(emptyList(), emptyList()); assertNull(lookup.get("object.child")); } public void testMaxDynamicKeyDepth() { { - FieldTypeLookup lookup = new FieldTypeLookup(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + FieldTypeLookup lookup = new FieldTypeLookup(emptyList(), emptyList()); assertEquals(0, lookup.getMaxParentPathDots()); } // Add a flattened object field. { String name = "object1.object2.field"; - FieldTypeLookup lookup = new FieldTypeLookup( - Collections.singletonList(createFlattenedMapper(name)), - Collections.emptyList(), - Collections.emptyList() - ); + FieldTypeLookup lookup = new FieldTypeLookup(Collections.singletonList(createFlattenedMapper(name)), emptyList()); assertEquals(2, lookup.getMaxParentPathDots()); } @@ -330,8 +332,7 @@ public void testMaxDynamicKeyDepth() { String name = "object1.object2.field"; FieldTypeLookup lookup = new FieldTypeLookup( Collections.singletonList(createFlattenedMapper(name)), - Collections.singletonList(new FieldAliasMapper("alias", "alias", "object1.object2.field")), - Collections.emptyList() + Collections.singletonList(new FieldAliasMapper("alias", "alias", "object1.object2.field")) ); assertEquals(2, lookup.getMaxParentPathDots()); } @@ -341,8 +342,7 @@ public void testMaxDynamicKeyDepth() { 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() + Collections.singletonList(new FieldAliasMapper("alias", "object1.object2.object3.alias", "object1.object2.field")) ); assertEquals(2, lookup.getMaxParentPathDots()); } @@ -353,8 +353,9 @@ public void testRuntimeFieldNameClashes() { IllegalArgumentException iae = expectThrows( IllegalArgumentException.class, () -> new FieldTypeLookup( - Collections.emptySet(), - Collections.emptySet(), + emptyList(), + emptyList(), + emptyList(), List.of(new TestRuntimeField("field", "type"), new TestRuntimeField("field", "long")) ) ); @@ -368,7 +369,7 @@ public void testRuntimeFieldNameClashes() { TestRuntimeField runtime = new TestRuntimeField("multi.first", "runtime"); IllegalArgumentException iae = expectThrows( IllegalArgumentException.class, - () -> new FieldTypeLookup(Collections.emptySet(), Collections.emptySet(), List.of(multi, runtime)) + () -> new FieldTypeLookup(emptyList(), emptyList(), emptyList(), List.of(multi, runtime)) ); assertEquals(iae.getMessage(), "Found two runtime fields with same name [multi.first]"); } @@ -383,7 +384,7 @@ public void testRuntimeFieldNameClashes() { IllegalArgumentException iae = expectThrows( IllegalArgumentException.class, - () -> new FieldTypeLookup(Collections.emptySet(), Collections.emptySet(), List.of(multi)) + () -> new FieldTypeLookup(emptyList(), emptyList(), emptyList(), List.of(multi)) ); assertEquals(iae.getMessage(), "Found two runtime fields with same name [multi]"); } @@ -401,7 +402,7 @@ public void testRuntimeFieldNameOutsideContext() { ); IllegalStateException ise = expectThrows( IllegalStateException.class, - () -> new FieldTypeLookup(Collections.emptySet(), Collections.emptySet(), Collections.singletonList(multi)) + () -> new FieldTypeLookup(emptyList(), emptyList(), emptyList(), Collections.singletonList(multi)) ); assertEquals("Found sub-fields with name not belonging to the parent field they are part of [first, second]", ise.getMessage()); } @@ -415,7 +416,7 @@ public void testRuntimeFieldNameOutsideContext() { ); IllegalStateException ise = expectThrows( IllegalStateException.class, - () -> new FieldTypeLookup(Collections.emptySet(), Collections.emptySet(), Collections.singletonList(multi)) + () -> new FieldTypeLookup(emptyList(), emptyList(), emptyList(), Collections.singletonList(multi)) ); assertEquals("Found sub-fields with name not belonging to the parent field they are part of [multi.]", ise.getMessage()); } @@ -424,4 +425,88 @@ public void testRuntimeFieldNameOutsideContext() { private static FlattenedFieldMapper createFlattenedMapper(String fieldName) { return new FlattenedFieldMapper.Builder(fieldName).build(MapperBuilderContext.root(false, false)); } + + private PassThroughObjectMapper createPassThroughMapper(String name, Map mappers, int priority) { + return new PassThroughObjectMapper( + name, + name, + Explicit.EXPLICIT_TRUE, + ObjectMapper.Dynamic.FALSE, + mappers, + Explicit.EXPLICIT_FALSE, + priority + ); + } + + public void testAddRootAliasesForPassThroughFields() { + MockFieldMapper foo = new MockFieldMapper("attributes.foo"); + MockFieldMapper bar = new MockFieldMapper( + new MockFieldMapper.FakeFieldType("attributes.much.more.deeply.nested.bar"), + "much.more.deeply.nested.bar" + ); + PassThroughObjectMapper attributes = createPassThroughMapper( + "attributes", + Map.of("foo", foo, "much.more.deeply.nested.bar", bar), + 0 + ); + + MockFieldMapper baz = new MockFieldMapper("resource.attributes.baz"); + MockFieldMapper bag = new MockFieldMapper( + new MockFieldMapper.FakeFieldType("resource.attributes.much.more.deeply.nested.bag"), + "much.more.deeply.nested.bag" + ); + PassThroughObjectMapper resourceAttributes = createPassThroughMapper( + "resource.attributes", + Map.of("baz", baz, "much.more.deeply.nested.bag", bag), + 1 + ); + + FieldTypeLookup lookup = new FieldTypeLookup( + List.of(foo, bar, baz, bag), + List.of(), + List.of(attributes, resourceAttributes), + List.of() + ); + assertEquals(foo.fieldType(), lookup.get("foo")); + assertEquals(bar.fieldType(), lookup.get("much.more.deeply.nested.bar")); + assertEquals(baz.fieldType(), lookup.get("baz")); + assertEquals(bag.fieldType(), lookup.get("much.more.deeply.nested.bag")); + } + + public void testNoPassThroughField() { + MockFieldMapper field = new MockFieldMapper("labels.foo"); + PassThroughObjectMapper attributes = createPassThroughMapper("attributes", Map.of(), 0); + + FieldTypeLookup lookup = new FieldTypeLookup(List.of(field), List.of(), List.of(attributes), List.of()); + assertNull(lookup.get("foo")); + } + + public void testAddRootAliasForConflictingPassThroughFields() { + MockFieldMapper attributeField = new MockFieldMapper("attributes.foo"); + PassThroughObjectMapper attributes = createPassThroughMapper("attributes", Map.of("foo", attributeField), 1); + + MockFieldMapper resourceAttributeField = new MockFieldMapper("resource.attributes.foo"); + PassThroughObjectMapper resourceAttributes = createPassThroughMapper( + "resource.attributes", + Map.of("foo", resourceAttributeField), + 0 + ); + + FieldTypeLookup lookup = new FieldTypeLookup( + List.of(attributeField, resourceAttributeField), + List.of(), + List.of(attributes, resourceAttributes), + List.of() + ); + assertEquals(attributeField.fieldType(), lookup.get("foo")); + } + + public void testNoRootAliasForPassThroughFieldOnConflictingField() { + MockFieldMapper attributeFoo = new MockFieldMapper("attributes.foo"); + MockFieldMapper foo = new MockFieldMapper("foo"); + PassThroughObjectMapper attributes = createPassThroughMapper("attributes", Map.of("foo", attributeFoo), 0); + + FieldTypeLookup lookup = new FieldTypeLookup(List.of(foo, attributeFoo), List.of(), List.of(attributes), List.of()); + assertEquals(foo.fieldType(), lookup.get("foo")); + } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java b/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java index 65fa4e236bafc..251b0ae62f3c5 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java @@ -47,7 +47,7 @@ private static MappingLookup createMappingLookup( new MetadataFieldMapper[0], Collections.emptyMap() ); - return MappingLookup.fromMappers(mapping, fieldMappers, objectMappers, emptyList()); + return MappingLookup.fromMappers(mapping, fieldMappers, objectMappers); } public void testOnlyRuntimeField() { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/PassThroughObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/PassThroughObjectMapperTests.java index b49ed2cf99df6..32899375dfaf0 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/PassThroughObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/PassThroughObjectMapperTests.java @@ -8,16 +8,20 @@ package org.elasticsearch.index.mapper; +import org.elasticsearch.common.Explicit; + import java.io.IOException; +import java.util.List; +import java.util.Map; -import static org.hamcrest.Matchers.equalTo; +import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.instanceOf; public class PassThroughObjectMapperTests extends MapperServiceTestCase { public void testSimpleKeyword() throws IOException { MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("labels").field("type", "passthrough"); + b.startObject("labels").field("type", "passthrough").field("priority", "0"); { b.startObject("properties"); b.startObject("dim").field("type", "keyword").endObject(); @@ -32,7 +36,7 @@ public void testSimpleKeyword() throws IOException { public void testKeywordDimension() throws IOException { MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("labels").field("type", "passthrough").field("time_series_dimension", "true"); + b.startObject("labels").field("type", "passthrough").field("priority", "0").field("time_series_dimension", "true"); { b.startObject("properties"); b.startObject("dim").field("type", "keyword").endObject(); @@ -45,9 +49,50 @@ public void testKeywordDimension() throws IOException { assertTrue(((KeywordFieldMapper) mapper).fieldType().isDimension()); } + public void testMissingPriority() throws IOException { + MapperException e = expectThrows(MapperException.class, () -> createMapperService(mapping(b -> { + b.startObject("labels").field("type", "passthrough"); + { + b.startObject("properties"); + b.startObject("dim").field("type", "keyword").endObject(); + b.endObject(); + } + b.endObject(); + }))); + assertThat(e.getMessage(), containsString("Pass-through object [labels] is missing a non-negative value for parameter [priority]")); + } + + public void testNegativePriority() throws IOException { + MapperException e = expectThrows(MapperException.class, () -> createMapperService(mapping(b -> { + b.startObject("labels").field("type", "passthrough").field("priority", "-1"); + { + b.startObject("properties"); + b.startObject("dim").field("type", "keyword").endObject(); + b.endObject(); + } + b.endObject(); + }))); + assertThat(e.getMessage(), containsString("Pass-through object [labels] is missing a non-negative value for parameter [priority]")); + } + + public void testPriorityParamSet() throws IOException { + MapperService mapperService = createMapperService(mapping(b -> { + b.startObject("labels").field("type", "passthrough").field("priority", "10"); + { + b.startObject("properties"); + b.startObject("dim").field("type", "keyword").endObject(); + b.endObject(); + } + b.endObject(); + })); + Mapper mapper = mapperService.mappingLookup().objectMappers().get("labels"); + assertThat(mapper, instanceOf(PassThroughObjectMapper.class)); + assertEquals(10, ((PassThroughObjectMapper) mapper).priority()); + } + public void testDynamic() throws IOException { MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("labels").field("type", "passthrough").field("dynamic", "true"); + b.startObject("labels").field("type", "passthrough").field("priority", "0").field("dynamic", "true"); { b.startObject("properties"); b.startObject("dim").field("type", "keyword").endObject(); @@ -61,7 +106,7 @@ public void testDynamic() throws IOException { public void testEnabled() throws IOException { MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("labels").field("type", "passthrough").field("enabled", "false"); + b.startObject("labels").field("type", "passthrough").field("priority", "0").field("enabled", "false"); { b.startObject("properties"); b.startObject("dim").field("type", "keyword").endObject(); @@ -92,7 +137,7 @@ public void testSubobjectsThrows() throws IOException { public void testAddSubobjectAutoFlatten() throws IOException { MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("labels").field("type", "passthrough").field("time_series_dimension", "true"); + b.startObject("labels").field("type", "passthrough").field("priority", "0").field("time_series_dimension", "true"); { b.startObject("properties"); { @@ -116,19 +161,55 @@ public void testAddSubobjectAutoFlatten() throws IOException { public void testWithoutMappers() throws IOException { MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("labels").field("type", "passthrough"); + b.startObject("labels").field("type", "passthrough").field("priority", "1"); { b.startObject("properties"); b.startObject("dim").field("type", "keyword").endObject(); b.endObject(); } b.endObject(); - b.startObject("shallow").field("type", "passthrough"); + b.startObject("shallow").field("type", "passthrough").field("priority", "2"); b.endObject(); })); - var labels = mapperService.mappingLookup().objectMappers().get("labels"); - var shallow = mapperService.mappingLookup().objectMappers().get("shallow"); - assertThat(labels.withoutMappers().toString(), equalTo(shallow.toString().replace("shallow", "labels"))); + assertEquals("passthrough", mapperService.mappingLookup().objectMappers().get("labels").typeName()); + assertEquals("passthrough", mapperService.mappingLookup().objectMappers().get("shallow").typeName()); + } + + public void testCheckForDuplicatePrioritiesEmpty() throws IOException { + PassThroughObjectMapper.checkForDuplicatePriorities(List.of()); + } + + private PassThroughObjectMapper create(String name, int priority) { + return new PassThroughObjectMapper( + name, + name, + Explicit.EXPLICIT_TRUE, + ObjectMapper.Dynamic.FALSE, + Map.of(), + Explicit.EXPLICIT_FALSE, + priority + ); + } + + public void testCheckForDuplicatePrioritiesOneElement() throws IOException { + PassThroughObjectMapper.checkForDuplicatePriorities(List.of(create("foo", 0))); + PassThroughObjectMapper.checkForDuplicatePriorities(List.of(create("foo", 10))); + } + + public void testCheckForDuplicatePrioritiesManyValidElements() throws IOException { + PassThroughObjectMapper.checkForDuplicatePriorities( + List.of(create("foo", 1), create("bar", 2), create("baz", 3), create("bar", 4)) + ); + } + + public void testCheckForDuplicatePrioritiesManyElementsDuplicatePriority() throws IOException { + MapperException e = expectThrows( + MapperException.class, + () -> PassThroughObjectMapper.checkForDuplicatePriorities( + List.of(create("foo", 1), create("bar", 1), create("baz", 3), create("bar", 4)) + ) + ); + assertThat(e.getMessage(), containsString("Pass-through object [bar] has a conflicting param [priority=1] with object [foo]")); } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/RootObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/RootObjectMapperTests.java index 438ffcc730918..80f3d00da34a2 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/RootObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/RootObjectMapperTests.java @@ -8,11 +8,9 @@ package org.elasticsearch.index.mapper; -import org.elasticsearch.common.Explicit; import org.elasticsearch.common.Strings; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.core.CheckedConsumer; -import org.elasticsearch.index.IndexVersion; import org.elasticsearch.index.mapper.MapperService.MergeReason; import org.elasticsearch.xcontent.XContentBuilder; import org.elasticsearch.xcontent.XContentFactory; @@ -20,8 +18,6 @@ import java.io.IOException; import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; -import java.util.Map; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; @@ -346,264 +342,6 @@ public void testRuntimeSectionRemainingField() throws IOException { assertEquals("Failed to parse mapping: unknown parameter [unsupported] on runtime field [field] of type [keyword]", e.getMessage()); } - public void testPassThroughObjectWithAliases() throws IOException { - MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("labels").field("type", "passthrough"); - { - b.startObject("properties"); - b.startObject("dim").field("type", "keyword").endObject(); - b.endObject(); - } - b.endObject(); - })); - assertThat(mapperService.mappingLookup().getMapper("dim"), instanceOf(FieldAliasMapper.class)); - assertThat(mapperService.mappingLookup().getMapper("labels.dim"), instanceOf(KeywordFieldMapper.class)); - } - - public void testPassThroughObjectNested() throws IOException { - MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("resource").field("type", "object"); - { - b.startObject("properties"); - { - b.startObject("attributes").field("type", "passthrough"); - { - b.startObject("properties"); - b.startObject("dim").field("type", "keyword").endObject(); - b.endObject(); - } - b.endObject(); - } - b.endObject(); - } - b.endObject(); - b.startObject("attributes").field("type", "passthrough"); - { - b.startObject("properties"); - b.startObject("another.dim").field("type", "keyword").endObject(); - b.endObject(); - } - b.endObject(); - })); - assertThat(mapperService.mappingLookup().getMapper("dim"), instanceOf(FieldAliasMapper.class)); - assertThat(mapperService.mappingLookup().getMapper("resource.attributes.dim"), instanceOf(KeywordFieldMapper.class)); - assertThat(mapperService.mappingLookup().objectMappers().get("another").getMapper("dim"), instanceOf(FieldAliasMapper.class)); - assertThat(mapperService.mappingLookup().getMapper("attributes.another.dim"), instanceOf(KeywordFieldMapper.class)); - } - - public void testPassThroughObjectNestedWithDuplicateNames() throws IOException { - MapperService mapperService = createMapperService(mapping(b -> { - b.startObject("resource").field("type", "object"); - { - b.startObject("properties"); - { - b.startObject("attributes").field("type", "passthrough"); - { - b.startObject("properties"); - b.startObject("dim").field("type", "keyword").endObject(); - b.startObject("more.attributes.another.dimA").field("type", "keyword").endObject(); - b.startObject("more.attributes.another.dimB").field("type", "keyword").endObject(); - b.endObject(); - } - b.endObject(); - } - b.endObject(); - } - b.endObject(); - b.startObject("attributes").field("type", "passthrough"); - { - b.startObject("properties"); - b.startObject("another.dim").field("type", "keyword").endObject(); - b.startObject("more.attributes.another.dimC").field("type", "keyword").endObject(); - b.startObject("more.attributes.another.dimD").field("type", "keyword").endObject(); - b.endObject(); - } - b.endObject(); - })); - - assertThat(mapperService.mappingLookup().getMapper("dim"), instanceOf(FieldAliasMapper.class)); - assertThat(mapperService.mappingLookup().getMapper("resource.attributes.dim"), instanceOf(KeywordFieldMapper.class)); - assertThat( - mapperService.mappingLookup().objectMappers().get("more.attributes.another").getMapper("dimA"), - instanceOf(FieldAliasMapper.class) - ); - assertThat( - mapperService.mappingLookup().getMapper("resource.attributes.more.attributes.another.dimA"), - instanceOf(KeywordFieldMapper.class) - ); - assertThat( - mapperService.mappingLookup().objectMappers().get("more.attributes.another").getMapper("dimB"), - instanceOf(FieldAliasMapper.class) - ); - assertThat( - mapperService.mappingLookup().getMapper("resource.attributes.more.attributes.another.dimB"), - instanceOf(KeywordFieldMapper.class) - ); - - assertThat(mapperService.mappingLookup().objectMappers().get("another").getMapper("dim"), instanceOf(FieldAliasMapper.class)); - assertThat(mapperService.mappingLookup().getMapper("attributes.another.dim"), instanceOf(KeywordFieldMapper.class)); - assertThat( - mapperService.mappingLookup().objectMappers().get("more.attributes.another").getMapper("dimC"), - instanceOf(FieldAliasMapper.class) - ); - assertThat( - mapperService.mappingLookup().getMapper("attributes.more.attributes.another.dimC"), - instanceOf(KeywordFieldMapper.class) - ); - assertThat( - mapperService.mappingLookup().objectMappers().get("more.attributes.another").getMapper("dimD"), - instanceOf(FieldAliasMapper.class) - ); - assertThat( - mapperService.mappingLookup().getMapper("attributes.more.attributes.another.dimD"), - instanceOf(KeywordFieldMapper.class) - ); - } - - public void testPassThroughObjectNestedWithConflictingNames() throws IOException { - MapperParsingException e = expectThrows(MapperParsingException.class, () -> createMapperService(mapping(b -> { - b.startObject("resource").field("type", "object"); - { - b.startObject("properties"); - { - b.startObject("attributes").field("type", "passthrough"); - { - b.startObject("properties"); - b.startObject("dim").field("type", "keyword").endObject(); - b.startObject("resource.attributes.another.dim").field("type", "keyword").endObject(); - b.endObject(); - } - b.endObject(); - } - b.endObject(); - } - b.endObject(); - }))); - assertEquals( - "Failed to parse mapping: Conflicting objects created during alias generation for pass-through field: [resource]", - e.getMessage() - ); - } - - public void testAliasMappersCreatesAlias() throws Exception { - var context = MapperBuilderContext.root(false, false); - Map aliases = new RootObjectMapper.Builder("root", Explicit.EXPLICIT_FALSE).getAliasMappers( - Map.of( - "labels", - new PassThroughObjectMapper( - "labels", - "labels", - Explicit.EXPLICIT_TRUE, - ObjectMapper.Dynamic.FALSE, - Map.of("host", new KeywordFieldMapper.Builder("host", IndexVersion.current()).build(context)), - Explicit.EXPLICIT_FALSE - ) - ), - context - ); - assertEquals(1, aliases.size()); - assertThat(aliases.get("host"), instanceOf(FieldAliasMapper.class)); - } - - public void testAliasMappersCreatesAliasNested() throws Exception { - var context = MapperBuilderContext.root(false, false); - Map aliases = new RootObjectMapper.Builder("root", Explicit.EXPLICIT_FALSE).getAliasMappers( - Map.of( - "outer", - new ObjectMapper( - "outer", - "outer", - Explicit.EXPLICIT_TRUE, - Explicit.EXPLICIT_TRUE, - Explicit.EXPLICIT_FALSE, - ObjectMapper.Dynamic.FALSE, - Map.of( - "inner", - new PassThroughObjectMapper( - "inner", - "outer.inner", - Explicit.EXPLICIT_TRUE, - ObjectMapper.Dynamic.FALSE, - Map.of("host", new KeywordFieldMapper.Builder("host", IndexVersion.current()).build(context)), - Explicit.EXPLICIT_FALSE - ) - ) - ) - ), - context - ); - assertEquals(1, aliases.size()); - assertThat(aliases.get("host"), instanceOf(FieldAliasMapper.class)); - } - - public void testAliasMappersExitsInDeepNesting() throws Exception { - var context = MapperBuilderContext.root(false, false); - Map aliases = new HashMap<>(); - var objectIntermediates = new HashMap(1); - var objectIntermediatesFullPath = new HashMap(1); - new RootObjectMapper.Builder("root", Explicit.EXPLICIT_FALSE).getAliasMappers( - Map.of( - "labels", - new PassThroughObjectMapper( - "labels", - "labels", - Explicit.EXPLICIT_TRUE, - ObjectMapper.Dynamic.FALSE, - Map.of("host", new KeywordFieldMapper.Builder("host", IndexVersion.current()).build(context)), - Explicit.EXPLICIT_FALSE - ) - ), - Map.of(), - aliases, - objectIntermediates, - objectIntermediatesFullPath, - context, - 1_000_000 - ); - assertTrue(aliases.isEmpty()); - } - - public void testAliasMappersCreatesNoAliasForRegularObject() throws Exception { - var context = MapperBuilderContext.root(false, false); - Map aliases = new RootObjectMapper.Builder("root", Explicit.EXPLICIT_FALSE).getAliasMappers( - Map.of( - "labels", - new ObjectMapper( - "labels", - "labels", - Explicit.EXPLICIT_TRUE, - Explicit.EXPLICIT_FALSE, - Explicit.EXPLICIT_FALSE, - ObjectMapper.Dynamic.FALSE, - Map.of("host", new KeywordFieldMapper.Builder("host", IndexVersion.current()).build(context)) - ) - ), - context - ); - assertTrue(aliases.isEmpty()); - } - - public void testAliasMappersConflictingField() throws Exception { - var context = MapperBuilderContext.root(false, false); - Map aliases = new RootObjectMapper.Builder("root", Explicit.EXPLICIT_FALSE).getAliasMappers( - Map.of( - "labels", - new PassThroughObjectMapper( - "labels", - "labels", - Explicit.EXPLICIT_TRUE, - ObjectMapper.Dynamic.FALSE, - Map.of("host", new KeywordFieldMapper.Builder("host", IndexVersion.current()).build(context)), - Explicit.EXPLICIT_FALSE - ), - "host", - new KeywordFieldMapper.Builder("host", IndexVersion.current()).build(context) - ), - context - ); - assertTrue(aliases.isEmpty()); - } - public void testEmptyType() throws Exception { String mapping = Strings.toString( XContentFactory.jsonBuilder() diff --git a/server/src/test/java/org/elasticsearch/index/query/SearchExecutionContextTests.java b/server/src/test/java/org/elasticsearch/index/query/SearchExecutionContextTests.java index 8d260ca7f8556..6d8a22e7850e4 100644 --- a/server/src/test/java/org/elasticsearch/index/query/SearchExecutionContextTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/SearchExecutionContextTests.java @@ -296,7 +296,7 @@ private static MappingLookup createMappingLookup(List concreteF new MetadataFieldMapper[0], Collections.emptyMap() ); - return MappingLookup.fromMappers(mapping, mappers, Collections.emptyList(), Collections.emptyList()); + return MappingLookup.fromMappers(mapping, mappers, Collections.emptyList()); } public void testSearchRequestRuntimeFields() { diff --git a/server/src/test/java/org/elasticsearch/indices/IndicesRequestCacheTests.java b/server/src/test/java/org/elasticsearch/indices/IndicesRequestCacheTests.java index 590dc72e2a72b..e8f6061efb8d9 100644 --- a/server/src/test/java/org/elasticsearch/indices/IndicesRequestCacheTests.java +++ b/server/src/test/java/org/elasticsearch/indices/IndicesRequestCacheTests.java @@ -203,7 +203,7 @@ public void testCacheDifferentReaders() throws Exception { public void testCacheDifferentMapping() throws Exception { IndicesRequestCache cache = new IndicesRequestCache(Settings.EMPTY); MappingLookup.CacheKey mappingKey1 = MappingLookup.EMPTY.cacheKey(); - MappingLookup.CacheKey mappingKey2 = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList(), emptyList()).cacheKey(); + MappingLookup.CacheKey mappingKey2 = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList()).cacheKey(); AtomicBoolean indexShard = new AtomicBoolean(true); ShardRequestCache requestCacheStats = new ShardRequestCache(); Directory dir = newDirectory(); @@ -363,14 +363,13 @@ public void testClearAllEntityIdentity() throws Exception { writer.updateDocument(new Term("id", "0"), newDoc(0, "bar")); DirectoryReader secondReader = ElasticsearchDirectoryReader.wrap(DirectoryReader.open(writer), new ShardId("foo", "bar", 1)); - MappingLookup.CacheKey secondMappingKey = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList(), emptyList()) - .cacheKey(); + MappingLookup.CacheKey secondMappingKey = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList()).cacheKey(); TestEntity secondEntity = new TestEntity(requestCacheStats, indexShard); Loader secondLoader = new Loader(secondReader, 0); writer.updateDocument(new Term("id", "0"), newDoc(0, "baz")); DirectoryReader thirdReader = ElasticsearchDirectoryReader.wrap(DirectoryReader.open(writer), new ShardId("foo", "bar", 1)); - MappingLookup.CacheKey thirdMappingKey = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList(), emptyList()).cacheKey(); + MappingLookup.CacheKey thirdMappingKey = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList()).cacheKey(); AtomicBoolean differentIdentity = new AtomicBoolean(true); TestEntity thirdEntity = new TestEntity(requestCacheStats, differentIdentity); Loader thirdLoader = new Loader(thirdReader, 0); @@ -506,7 +505,7 @@ public void testKeyEqualsAndHashCode() throws IOException { AtomicBoolean trueBoolean = new AtomicBoolean(true); AtomicBoolean falseBoolean = new AtomicBoolean(false); MappingLookup.CacheKey mKey1 = MappingLookup.EMPTY.cacheKey(); - MappingLookup.CacheKey mKey2 = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList(), emptyList()).cacheKey(); + MappingLookup.CacheKey mKey2 = MappingLookup.fromMappers(Mapping.EMPTY, emptyList(), emptyList()).cacheKey(); Directory dir = newDirectory(); IndexWriterConfig config = newIndexWriterConfig(); IndexWriter writer = new IndexWriter(dir, config); diff --git a/server/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java b/server/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java index a84df1ba2acba..1953ae47b0f5a 100644 --- a/server/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java +++ b/server/src/test/java/org/elasticsearch/search/suggest/AbstractSuggestionBuilderTestCase.java @@ -168,7 +168,7 @@ public void testBuild() throws IOException { invocation -> new TestTemplateService.MockTemplateScript.Factory(((Script) invocation.getArguments()[0]).getIdOrCode()) ); List mappers = Collections.singletonList(new MockFieldMapper(fieldType)); - MappingLookup lookup = MappingLookup.fromMappers(Mapping.EMPTY, mappers, emptyList(), emptyList()); + MappingLookup lookup = MappingLookup.fromMappers(Mapping.EMPTY, mappers, emptyList()); SearchExecutionContext mockContext = new SearchExecutionContext( 0, 0, diff --git a/test/framework/src/main/java/org/elasticsearch/cluster/metadata/DataStreamTestHelper.java b/test/framework/src/main/java/org/elasticsearch/cluster/metadata/DataStreamTestHelper.java index c78ed54c13d8f..4b2617a0d08fc 100644 --- a/test/framework/src/main/java/org/elasticsearch/cluster/metadata/DataStreamTestHelper.java +++ b/test/framework/src/main/java/org/elasticsearch/cluster/metadata/DataStreamTestHelper.java @@ -671,7 +671,7 @@ public static MetadataRolloverService getMetadataRolloverService( new MetadataFieldMapper[] { dtfm }, Collections.emptyMap() ); - mappingLookup = MappingLookup.fromMappers(mapping, List.of(dtfm, dateFieldMapper), List.of(), List.of()); + mappingLookup = MappingLookup.fromMappers(mapping, List.of(dtfm, dateFieldMapper), List.of()); } IndicesService indicesService = mockIndicesServices(mappingLookup); diff --git a/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java b/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java index cb028c746a8cd..b95619602573c 100644 --- a/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java +++ b/test/framework/src/main/java/org/elasticsearch/index/mapper/MockFieldMapper.java @@ -24,6 +24,10 @@ public MockFieldMapper(MappedFieldType fieldType) { this(findSimpleName(fieldType.name()), fieldType, MultiFields.empty(), CopyTo.empty()); } + public MockFieldMapper(MappedFieldType fieldType, String simpleName) { + super(simpleName, fieldType, MultiFields.empty(), CopyTo.empty(), false, null); + } + public MockFieldMapper(String fullName, MappedFieldType fieldType, MultiFields multifields, CopyTo copyTo) { super(findSimpleName(fullName), fieldType, multifields, copyTo, false, null); } diff --git a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java index 1f04b60efc8ae..fbf0d1f0d9ac1 100644 --- a/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/search/aggregations/AggregatorTestCase.java @@ -348,7 +348,8 @@ private AggregationContext createAggregationContext( // Alias all fields to -alias to test aliases Arrays.stream(fieldTypes) .map(ft -> new FieldAliasMapper(ft.name() + "-alias", ft.name() + "-alias", ft.name())) - .collect(toList()) + .collect(toList()), + List.of() ); BiFunction> fieldDataBuilder = (fieldType, context) -> fieldType .fielddataBuilder( @@ -461,7 +462,7 @@ private SubSearchContext buildSubSearchContext( * of stuff. */ SearchExecutionContext subContext = spy(searchExecutionContext); - MappingLookup disableNestedLookup = MappingLookup.fromMappers(Mapping.EMPTY, Set.of(), Set.of(), Set.of()); + MappingLookup disableNestedLookup = MappingLookup.fromMappers(Mapping.EMPTY, Set.of(), Set.of()); doReturn(new NestedDocuments(disableNestedLookup, bitsetFilterCache::getBitSetProducer, indexSettings.getIndexVersionCreated())) .when(subContext) .getNestedDocuments(); diff --git a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java index 9dfa98607851a..6598ac486d53e 100644 --- a/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java +++ b/x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/security/authz/accesscontrol/DocumentSubsetBitsetCacheTests.java @@ -632,7 +632,7 @@ private void runTestOnIndices(int numberIndices, CheckedConsumer concreteFields) { List mappers = concreteFields.stream().map(MockFieldMapper::new).collect(Collectors.toList()); - return MappingLookup.fromMappers(Mapping.EMPTY, mappers, emptyList(), emptyList()); + return MappingLookup.fromMappers(Mapping.EMPTY, mappers, emptyList()); } }