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 953d6d8d809e0..2054ed378c35c 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 @@ -40,6 +40,7 @@ import org.elasticsearch.index.mapper.Mapping; import org.elasticsearch.index.mapper.MappingLookup; import org.elasticsearch.index.mapper.MetadataFieldMapper; +import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.mapper.RootObjectMapper; import org.elasticsearch.indices.EmptySystemIndices; import org.elasticsearch.indices.IndicesService; @@ -218,7 +219,7 @@ public void setup() throws Exception { false, Version.CURRENT ).build(MapperBuilderContext.ROOT); - RootObjectMapper.Builder root = new RootObjectMapper.Builder("_doc"); + RootObjectMapper.Builder root = new RootObjectMapper.Builder("_doc", ObjectMapper.Defaults.SUBOBJECTS); root.add( new DateFieldMapper.Builder( "@timestamp", diff --git a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/index/91_metrics_no_subobjects.yml b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/index/91_metrics_no_subobjects.yml index 87fb867447077..4555adda67313 100644 --- a/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/index/91_metrics_no_subobjects.yml +++ b/rest-api-spec/src/yamlRestTest/resources/rest-api-spec/test/index/91_metrics_no_subobjects.yml @@ -1,5 +1,5 @@ --- -"Metrics indexing": +"Metrics object indexing": - skip: features: allowed_warnings_regex version: " - 8.2.99" @@ -17,6 +17,9 @@ mapping: type: object subobjects: false + properties: + host.name: + type: keyword - do: allowed_warnings_regex: @@ -26,15 +29,62 @@ id: 1 refresh: true body: - { metrics.time: 10, metrics.time.max: 100, metrics.time.min: 1 } + { metrics.host.name: localhost, metrics.host.id: 1, metrics.time: 10, metrics.time.max: 100, metrics.time.min: 1 } - do: field_caps: index: test-1 - fields: metrics.time* + fields: metrics* + - match: {fields.metrics\.host\.id.long.searchable: true} + - match: {fields.metrics\.host\.id.long.aggregatable: true} + - match: {fields.metrics\.host\.name.keyword.searchable: true} + - match: {fields.metrics\.host\.name.keyword.aggregatable: true} - match: {fields.metrics\.time.long.searchable: true} - match: {fields.metrics\.time.long.aggregatable: true} - match: {fields.metrics\.time\.max.long.searchable: true} - match: {fields.metrics\.time\.max.long.aggregatable: true} - match: {fields.metrics\.time\.min.long.searchable: true} - match: {fields.metrics\.time\.min.long.aggregatable: true} + +--- +"Root without subobjects": + - skip: + features: allowed_warnings_regex + version: " - 8.2.99" + reason: added in 8.3.0 + + - do: + indices.put_template: + name: test + body: + index_patterns: test-* + mappings: + subobjects: false + properties: + host.name: + type: keyword + + - do: + allowed_warnings_regex: + - "index \\[test-1\\] matches multiple legacy templates \\[global, test\\], composable templates will only match a single template" + index: + index: test-1 + id: 1 + refresh: true + body: + { host.name: localhost, host.id: 1, time: 10, time.max: 100, time.min: 1 } + + - do: + field_caps: + index: test-1 + fields: [host*, time*] + - match: {fields.host\.name.keyword.searchable: true} + - match: {fields.host\.name.keyword.aggregatable: true} + - match: {fields.host\.id.long.searchable: true} + - match: {fields.host\.id.long.aggregatable: true} + - match: {fields.time.long.searchable: true} + - match: {fields.time.long.aggregatable: true} + - match: {fields.time\.max.long.searchable: true} + - match: {fields.time\.max.long.aggregatable: true} + - match: {fields.time\.min.long.searchable: true} + - match: {fields.time\.min.long.aggregatable: true} diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/mapper/DynamicMappingIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/mapper/DynamicMappingIT.java index e8c15bed70605..b330f08f2e08e 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/mapper/DynamicMappingIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/mapper/DynamicMappingIT.java @@ -13,6 +13,7 @@ import org.elasticsearch.action.bulk.BulkResponse; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.index.IndexRequestBuilder; +import org.elasticsearch.action.index.IndexResponse; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.support.WriteRequest; import org.elasticsearch.cluster.ClusterState; @@ -494,4 +495,77 @@ public void testDynamicRuntimeObjectFields() { e.getMessage() ); } + + public void testSubobjectsFalseAtRoot() { + assertAcked(client().admin().indices().prepareCreate("test").setMapping(""" + { + "_doc": { + "subobjects" : false, + "properties": { + "host.name": { + "type": "keyword" + } + } + } + }""").get()); + + IndexRequest request = new IndexRequest("test").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source("host.name", "localhost", "host.id", 111, "time", 100, "time.max", 1000); + IndexResponse indexResponse = client().index(request).actionGet(); + assertEquals(RestStatus.CREATED, indexResponse.status()); + + Map mappings = client().admin().indices().prepareGetMappings("test").get().mappings().get("test").getSourceAsMap(); + @SuppressWarnings("unchecked") + Map properties = (Map) mappings.get("properties"); + assertEquals(4, properties.size()); + assertNotNull(properties.get("host.name")); + assertNotNull(properties.get("host.id")); + assertNotNull(properties.get("time")); + assertNotNull(properties.get("time.max")); + } + + @SuppressWarnings("unchecked") + public void testSubobjectsFalse() { + assertAcked(client().admin().indices().prepareCreate("test").setMapping(""" + { + "_doc": { + "properties": { + "foo.metrics" : { + "subobjects" : false, + "properties" : { + "host.name": { + "type": "keyword" + } + } + } + } + } + }""").get()); + + IndexRequest request = new IndexRequest("test").setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE) + .source( + "foo.metrics.host.name", + "localhost", + "foo.metrics.host.id", + 111, + "foo.metrics.time", + 100, + "foo.metrics.time.max", + 1000 + ); + IndexResponse indexResponse = client().index(request).actionGet(); + assertEquals(RestStatus.CREATED, indexResponse.status()); + + Map mappings = client().admin().indices().prepareGetMappings("test").get().mappings().get("test").getSourceAsMap(); + Map properties = (Map) mappings.get("properties"); + Map foo = (Map) properties.get("foo"); + properties = (Map) foo.get("properties"); + Map metrics = (Map) properties.get("metrics"); + properties = (Map) metrics.get("properties"); + assertEquals(4, properties.size()); + assertNotNull(properties.get("host.name")); + assertNotNull(properties.get("host.id")); + assertNotNull(properties.get("time")); + assertNotNull(properties.get("time.max")); + } } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java index 46e51d92a399b..bfa6ea4a675df 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DocumentMapper.java @@ -25,7 +25,9 @@ public class DocumentMapper { * @return the newly created document mapper */ public static DocumentMapper createEmpty(MapperService mapperService) { - RootObjectMapper root = new RootObjectMapper.Builder(MapperService.SINGLE_MAPPING_NAME).build(MapperBuilderContext.ROOT); + RootObjectMapper root = new RootObjectMapper.Builder(MapperService.SINGLE_MAPPING_NAME, ObjectMapper.Defaults.SUBOBJECTS).build( + MapperBuilderContext.ROOT + ); MetadataFieldMapper[] metadata = mapperService.getMetadataMappers().values().toArray(new MetadataFieldMapper[0]); Mapping mapping = new Mapping(root, metadata, null); return new DocumentMapper(mapperService.documentParser(), mapping, mapping.toCompressedXContent()); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldsBuilder.java b/server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldsBuilder.java index dd2baa686cfc1..212b5ba8b307c 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldsBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/DynamicFieldsBuilder.java @@ -161,7 +161,8 @@ static Mapper createDynamicObjectMapper(DocumentParserContext context, String na Mapper mapper = createObjectMapperFromTemplate(context, name); return mapper != null ? mapper - : new ObjectMapper.Builder(name).enabled(ObjectMapper.Defaults.ENABLED).build(context.createMapperBuilderContext()); + : new ObjectMapper.Builder(name, ObjectMapper.Defaults.SUBOBJECTS).enabled(ObjectMapper.Defaults.ENABLED) + .build(context.createMapperBuilderContext()); } /** diff --git a/server/src/main/java/org/elasticsearch/index/mapper/Mapping.java b/server/src/main/java/org/elasticsearch/index/mapper/Mapping.java index f2ce6ec3a6f34..a75347eb6f1b3 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/Mapping.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/Mapping.java @@ -32,7 +32,7 @@ public final class Mapping implements ToXContentFragment { public static final Mapping EMPTY = new Mapping( - new RootObjectMapper.Builder("_doc").build(MapperBuilderContext.ROOT), + new RootObjectMapper.Builder("_doc", ObjectMapper.Defaults.SUBOBJECTS).build(MapperBuilderContext.ROOT), new MetadataFieldMapper[0], null ); diff --git a/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java index b2a05e632804f..68142091cde22 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/NestedObjectMapper.java @@ -33,7 +33,7 @@ public static class Builder extends ObjectMapper.Builder { private final Version indexCreatedVersion; public Builder(String name, Version indexCreatedVersion) { - super(name); + super(name, Explicit.IMPLICIT_TRUE); this.indexCreatedVersion = indexCreatedVersion; } @@ -57,6 +57,9 @@ public static class TypeParser extends ObjectMapper.TypeParser { @Override public Mapper.Builder parse(String name, Map node, MappingParserContext parserContext) throws MapperParsingException { + if (parseSubobjects(node).explicit()) { + throw new MapperParsingException("Nested type [" + name + "] does not support [subobjects] parameter"); + } NestedObjectMapper.Builder builder = new NestedObjectMapper.Builder(name, parserContext.indexVersionCreated()); parseNested(name, node, builder); for (Iterator> iterator = node.entrySet().iterator(); iterator.hasNext();) { @@ -67,9 +70,6 @@ public Mapper.Builder parse(String name, Map node, MappingParser iterator.remove(); } } - if (builder.subobjects.explicit()) { - throw new MapperParsingException("Nested type [" + name + "] does not support [subobjects] parameter"); - } return builder; } diff --git a/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java index 303d5ded51e5c..fa83b62684f7b 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/ObjectMapper.java @@ -37,6 +37,7 @@ public class ObjectMapper extends Mapper implements Cloneable { public static class Defaults { public static final boolean ENABLED = true; + public static final Explicit SUBOBJECTS = Explicit.IMPLICIT_TRUE; } public enum Dynamic { @@ -61,14 +62,14 @@ DynamicFieldsBuilder getDynamicFieldsBuilder() { } public static class Builder extends Mapper.Builder { - + protected final Explicit subobjects; protected Explicit enabled = Explicit.IMPLICIT_TRUE; - protected Explicit subobjects = Explicit.IMPLICIT_TRUE; protected Dynamic dynamic; protected final List mappersBuilders = new ArrayList<>(); - public Builder(String name) { + public Builder(String name, Explicit subobjects) { super(name); + this.subobjects = subobjects; } public Builder enabled(boolean enabled) { @@ -76,11 +77,6 @@ public Builder enabled(boolean enabled) { return this; } - public Builder subobjects(boolean subobjects) { - this.subobjects = Explicit.explicitBoolean(subobjects); - return this; - } - public Builder dynamic(Dynamic dynamic) { this.dynamic = dynamic; return this; @@ -186,7 +182,8 @@ public boolean supportsVersion(Version indexCreatedVersion) { @Override public Mapper.Builder parse(String name, Map node, MappingParserContext parserContext) throws MapperParsingException { - ObjectMapper.Builder builder = new Builder(name); + Explicit subobjects = parseSubobjects(node); + ObjectMapper.Builder builder = new Builder(name, subobjects); for (Iterator> iterator = node.entrySet().iterator(); iterator.hasNext();) { Map.Entry entry = iterator.next(); String fieldName = entry.getKey(); @@ -219,9 +216,6 @@ protected static boolean parseObjectOrDocumentTypeProperties( } else if (fieldName.equals("enabled")) { builder.enabled(XContentMapValues.nodeBooleanValue(fieldNode, fieldName + ".enabled")); return true; - } else if (fieldName.equals("subobjects")) { - builder.subobjects(XContentMapValues.nodeBooleanValue(fieldNode, fieldName + ".subobjects")); - return true; } else if (fieldName.equals("properties")) { if (fieldNode instanceof Collection && ((Collection) fieldNode).isEmpty()) { // nothing to do here, empty (to support "properties: []" case) @@ -242,6 +236,14 @@ protected static boolean parseObjectOrDocumentTypeProperties( return false; } + protected static Explicit parseSubobjects(Map node) { + Object subobjectsNode = node.remove("subobjects"); + if (subobjectsNode != null) { + return Explicit.explicitBoolean(XContentMapValues.nodeBooleanValue(subobjectsNode, "subobjects.subobjects")); + } + return Explicit.IMPLICIT_TRUE; + } + protected static void parseProperties( ObjectMapper.Builder objBuilder, Map propsNode, @@ -298,7 +300,7 @@ protected static void parseProperties( String realFieldName = fieldNameParts[fieldNameParts.length - 1]; fieldBuilder = typeParser.parse(realFieldName, propNode, parserContext); for (int i = fieldNameParts.length - 2; i >= 0; --i) { - ObjectMapper.Builder intermediate = new ObjectMapper.Builder(fieldNameParts[i]); + ObjectMapper.Builder intermediate = new ObjectMapper.Builder(fieldNameParts[i], Defaults.SUBOBJECTS); intermediate.add(fieldBuilder); fieldBuilder = intermediate; } @@ -367,9 +369,8 @@ protected ObjectMapper clone() { * @return a Builder that will produce an empty ObjectMapper with the same configuration as this one */ public ObjectMapper.Builder newBuilder(Version indexVersionCreated) { - ObjectMapper.Builder builder = new ObjectMapper.Builder(simpleName()); + ObjectMapper.Builder builder = new ObjectMapper.Builder(simpleName(), subobjects); builder.enabled = this.enabled; - builder.subobjects = this.subobjects; builder.dynamic = this.dynamic; return builder; } @@ -515,7 +516,7 @@ void toXContent(XContentBuilder builder, Params params, ToXContent custom) throw if (isEnabled() != Defaults.ENABLED) { builder.field("enabled", enabled.value()); } - if (subobjects() == false) { + if (subobjects != Defaults.SUBOBJECTS) { builder.field("subobjects", subobjects.value()); } if (custom != null) { 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 f2a288550faac..f9b4cdcecbc94 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/RootObjectMapper.java @@ -70,8 +70,8 @@ public static class Builder extends ObjectMapper.Builder { protected Explicit dateDetection = Defaults.DATE_DETECTION; protected Explicit numericDetection = Defaults.NUMERIC_DETECTION; - public Builder(String name) { - super(name); + public Builder(String name, Explicit subobjects) { + super(name, subobjects); } public Builder dynamicDateTimeFormatter(Collection dateTimeFormatters) { @@ -152,7 +152,8 @@ static final class TypeParser extends ObjectMapper.TypeParser { @Override public RootObjectMapper.Builder parse(String name, Map node, MappingParserContext parserContext) throws MapperParsingException { - RootObjectMapper.Builder builder = new Builder(name); + Explicit subobjects = parseSubobjects(node); + RootObjectMapper.Builder builder = new Builder(name, subobjects); Iterator> iterator = node.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); @@ -278,9 +279,8 @@ protected ObjectMapper clone() { @Override public RootObjectMapper.Builder newBuilder(Version indexVersionCreated) { - RootObjectMapper.Builder builder = new RootObjectMapper.Builder(name()); + RootObjectMapper.Builder builder = new RootObjectMapper.Builder(name(), subobjects); builder.enabled = enabled; - builder.subobjects = subobjects; builder.dynamic = dynamic; return builder; } diff --git a/server/src/test/java/org/elasticsearch/cluster/action/index/MappingUpdatedActionTests.java b/server/src/test/java/org/elasticsearch/cluster/action/index/MappingUpdatedActionTests.java index 0ecbde836c47a..db34e85504f5a 100644 --- a/server/src/test/java/org/elasticsearch/cluster/action/index/MappingUpdatedActionTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/action/index/MappingUpdatedActionTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.index.mapper.MapperBuilderContext; import org.elasticsearch.index.mapper.Mapping; import org.elasticsearch.index.mapper.MetadataFieldMapper; +import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.mapper.RootObjectMapper; import org.elasticsearch.test.ESTestCase; @@ -146,7 +147,9 @@ public void testSendUpdateMappingUsingAutoPutMappingAction() { ); mua.setClient(client); - RootObjectMapper rootObjectMapper = new RootObjectMapper.Builder("name").build(MapperBuilderContext.ROOT); + RootObjectMapper rootObjectMapper = new RootObjectMapper.Builder("name", ObjectMapper.Defaults.SUBOBJECTS).build( + MapperBuilderContext.ROOT + ); Mapping update = new Mapping(rootObjectMapper, new MetadataFieldMapper[0], Map.of()); mua.sendUpdateMapping(new Index("name", "uuid"), update, ActionListener.noop()); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java b/server/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java index 4c37429d41dbd..bdd31b90096f1 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/DynamicMappingTests.java @@ -902,4 +902,46 @@ public void testArraysOfObjectsDynamicFalse() throws IOException { } }"""), Strings.toString(doc.dynamicMappingsUpdate())); } + + public void testSubobjectsFalseRootDynamicUpdate() throws Exception { + MapperService mapperService = createMapperService(topMapping(b -> { + b.field("subobjects", false); + b.startObject("properties"); + b.startObject("host.name").field("type", "keyword").endObject(); + b.endObject(); + })); + ParsedDocument doc = mapperService.documentMapper().parse(source(""" + { + "time" : 10, + "time.max" : 500, + "time.min" : 1, + "host.name" : "localhost", + "host.id" : 111 + } + """)); + + Mapping mappingsUpdate = doc.dynamicMappingsUpdate(); + assertNotNull(mappingsUpdate); + assertNotNull(mappingsUpdate.getRoot().getMapper("time")); + assertNotNull(mappingsUpdate.getRoot().getMapper("time.max")); + assertNotNull(mappingsUpdate.getRoot().getMapper("time.min")); + assertNotNull(mappingsUpdate.getRoot().getMapper("host.id")); + assertNull(mappingsUpdate.getRoot().getMapper("host.name")); + + assertNotNull(doc.rootDoc().getFields("time")); + assertNotNull(doc.rootDoc().getFields("time.max")); + assertNotNull(doc.rootDoc().getFields("time.min")); + assertNotNull(doc.rootDoc().getFields("host.name")); + assertNotNull(doc.rootDoc().getFields("host.id")); + + merge(mapperService, dynamicMapping(mappingsUpdate)); + + assertNotNull(mapperService.fieldType("time")); + assertNotNull(mapperService.fieldType("time.max")); + assertNotNull(mapperService.fieldType("time.min")); + assertNotNull(mapperService.fieldType("host.id")); + assertNotNull(mapperService.fieldType("host.name")); + + assertEquals(0, mapperService.mappingLookup().objectMappers().size()); + } } 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 095938af5820f..5e4b654bb14de 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldAliasMapperValidationTests.java @@ -175,7 +175,7 @@ private static MappingLookup createMappingLookup( List fieldAliasMappers, List runtimeFields ) { - RootObjectMapper.Builder builder = new RootObjectMapper.Builder("_doc"); + RootObjectMapper.Builder builder = new RootObjectMapper.Builder("_doc", ObjectMapper.Defaults.SUBOBJECTS); Map runtimeFieldTypes = runtimeFields.stream().collect(Collectors.toMap(RuntimeField::name, r -> r)); builder.addRuntimeFields(runtimeFieldTypes); Mapping mapping = new Mapping(builder.build(MapperBuilderContext.ROOT), new MetadataFieldMapper[0], Collections.emptyMap()); 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 fb13a2dc8cff4..287663d2c3a12 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/MappingLookupTests.java @@ -39,7 +39,7 @@ private static MappingLookup createMappingLookup( List objectMappers, List runtimeFields ) { - RootObjectMapper.Builder builder = new RootObjectMapper.Builder("_doc"); + RootObjectMapper.Builder builder = new RootObjectMapper.Builder("_doc", ObjectMapper.Defaults.SUBOBJECTS); Map runtimeFieldTypes = runtimeFields.stream().collect(Collectors.toMap(RuntimeField::name, r -> r)); builder.addRuntimeFields(runtimeFieldTypes); Mapping mapping = new Mapping(builder.build(MapperBuilderContext.ROOT), new MetadataFieldMapper[0], Collections.emptyMap()); diff --git a/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperMergeTests.java b/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperMergeTests.java index cb46a450ffc70..8627cd4c16598 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperMergeTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperMergeTests.java @@ -8,6 +8,7 @@ package org.elasticsearch.index.mapper; import org.elasticsearch.Version; +import org.elasticsearch.common.Explicit; import org.elasticsearch.test.ESTestCase; import java.util.Collections; @@ -71,7 +72,7 @@ public void testMergeDisabledField() { // GIVEN a mapping with "foo" field disabled Map mappers = new HashMap<>(); // the field is disabled, and we are not trying to re-enable it, hence merge should work - mappers.put("disabled", new ObjectMapper.Builder("disabled").build(MapperBuilderContext.ROOT)); + mappers.put("disabled", new ObjectMapper.Builder("disabled", ObjectMapper.Defaults.SUBOBJECTS).build(MapperBuilderContext.ROOT)); RootObjectMapper mergeWith = createRootObjectMapper("type1", true, Collections.unmodifiableMap(mappers)); RootObjectMapper merged = (RootObjectMapper) rootObjectMapper.merge(mergeWith, MapperBuilderContext.ROOT); @@ -102,10 +103,11 @@ public void testMergeEnabledForRootMapper() { public void testMergeDisabledRootMapper() { String type = MapperService.SINGLE_MAPPING_NAME; - final RootObjectMapper rootObjectMapper = (RootObjectMapper) new RootObjectMapper.Builder(type).enabled(false) + final RootObjectMapper rootObjectMapper = (RootObjectMapper) new RootObjectMapper.Builder(type, ObjectMapper.Defaults.SUBOBJECTS) + .enabled(false) .build(MapperBuilderContext.ROOT); // the root is disabled, and we are not trying to re-enable it, but we do want to be able to add runtime fields - final RootObjectMapper mergeWith = new RootObjectMapper.Builder(type).addRuntimeFields( + final RootObjectMapper mergeWith = new RootObjectMapper.Builder(type, ObjectMapper.Defaults.SUBOBJECTS).addRuntimeFields( Collections.singletonMap("test", new TestRuntimeField("test", "long")) ).build(MapperBuilderContext.ROOT); @@ -217,17 +219,21 @@ private static RootObjectMapper createRootSubobjectFalseLeafWithDots() { FieldMapper fieldMapper = new KeywordFieldMapper.Builder("host.name", Version.CURRENT).build(MapperBuilderContext.ROOT); assertEquals("host.name", fieldMapper.simpleName()); assertEquals("host.name", fieldMapper.name()); - return (RootObjectMapper) new RootObjectMapper.Builder("_doc").subobjects(false) - .addMappers(Collections.singletonMap("host.name", fieldMapper)) - .build(MapperBuilderContext.ROOT); + return (RootObjectMapper) new RootObjectMapper.Builder("_doc", Explicit.EXPLICIT_FALSE).addMappers( + Collections.singletonMap("host.name", fieldMapper) + ).build(MapperBuilderContext.ROOT); } private static RootObjectMapper createRootObjectMapper(String name, boolean enabled, Map mappers) { - return (RootObjectMapper) new RootObjectMapper.Builder(name).enabled(enabled).addMappers(mappers).build(MapperBuilderContext.ROOT); + return (RootObjectMapper) new RootObjectMapper.Builder(name, ObjectMapper.Defaults.SUBOBJECTS).enabled(enabled) + .addMappers(mappers) + .build(MapperBuilderContext.ROOT); } private static ObjectMapper createObjectMapper(String name, boolean enabled, Map mappers) { - return new ObjectMapper.Builder(name).enabled(enabled).addMappers(mappers).build(MapperBuilderContext.ROOT); + return new ObjectMapper.Builder(name, ObjectMapper.Defaults.SUBOBJECTS).enabled(enabled) + .addMappers(mappers) + .build(MapperBuilderContext.ROOT); } private static ObjectMapper createObjectSubobjectsFalseLeafWithDots() { @@ -236,10 +242,11 @@ private static ObjectMapper createObjectSubobjectsFalseLeafWithDots() { ); assertEquals("host.name", fieldMapper.simpleName()); assertEquals("foo.metrics.host.name", fieldMapper.name()); - ObjectMapper metrics = new ObjectMapper.Builder("metrics").subobjects(false) - .addMappers(Collections.singletonMap("host.name", fieldMapper)) - .build(new MapperBuilderContext("foo")); - return new ObjectMapper.Builder("foo").addMappers(Collections.singletonMap("metrics", metrics)).build(MapperBuilderContext.ROOT); + ObjectMapper metrics = new ObjectMapper.Builder("metrics", Explicit.EXPLICIT_FALSE).addMappers( + Collections.singletonMap("host.name", fieldMapper) + ).build(new MapperBuilderContext("foo")); + return new ObjectMapper.Builder("foo", ObjectMapper.Defaults.SUBOBJECTS).addMappers(Collections.singletonMap("metrics", metrics)) + .build(MapperBuilderContext.ROOT); } private ObjectMapper createObjectSubobjectsFalseLeafWithMultiField() { @@ -249,10 +256,11 @@ private ObjectMapper createObjectSubobjectsFalseLeafWithMultiField() { FieldMapper fieldMapper = textKeywordMultiField.multiFields.iterator().next(); assertEquals("keyword", fieldMapper.simpleName()); assertEquals("foo.metrics.host.name.keyword", fieldMapper.name()); - ObjectMapper metrics = new ObjectMapper.Builder("metrics").subobjects(false) - .addMappers(Collections.singletonMap("host.name", textKeywordMultiField)) - .build(new MapperBuilderContext("foo")); - return new ObjectMapper.Builder("foo").addMappers(Collections.singletonMap("metrics", metrics)).build(MapperBuilderContext.ROOT); + ObjectMapper metrics = new ObjectMapper.Builder("metrics", Explicit.EXPLICIT_FALSE).addMappers( + Collections.singletonMap("host.name", textKeywordMultiField) + ).build(new MapperBuilderContext("foo")); + return new ObjectMapper.Builder("foo", ObjectMapper.Defaults.SUBOBJECTS).addMappers(Collections.singletonMap("metrics", metrics)) + .build(MapperBuilderContext.ROOT); } private TextFieldMapper createTextFieldMapper(String name) { diff --git a/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java b/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java index 087fda94db5e3..2bce46aeeb665 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/ObjectMapperTests.java @@ -419,6 +419,11 @@ public void testSubobjectsFalseRoot() throws Exception { assertNotNull(mapperService.fieldType("metrics.service.time.max")); } + public void testExplicitDefaultSubobjects() throws Exception { + MapperService mapperService = createMapperService(topMapping(b -> b.field("subobjects", true))); + assertEquals("{\"_doc\":{\"subobjects\":true}}", Strings.toString(mapperService.mappingLookup().getMapping())); + } + public void testSubobjectsFalseRootWithInnerObject() { MapperParsingException exception = expectThrows(MapperParsingException.class, () -> createMapperService(topMapping(b -> { b.field("subobjects", false); 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 b2bbbe3898f0e..4f87376311452 100644 --- a/server/src/test/java/org/elasticsearch/index/query/SearchExecutionContextTests.java +++ b/server/src/test/java/org/elasticsearch/index/query/SearchExecutionContextTests.java @@ -53,6 +53,7 @@ import org.elasticsearch.index.mapper.MetadataFieldMapper; import org.elasticsearch.index.mapper.MockFieldMapper; import org.elasticsearch.index.mapper.NumberFieldMapper; +import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.mapper.RootObjectMapper; import org.elasticsearch.index.mapper.RuntimeField; import org.elasticsearch.index.mapper.TestRuntimeField; @@ -300,7 +301,7 @@ public void testFielddataLookupOneFieldManyReferences() throws IOException { private static MappingLookup createMappingLookup(List concreteFields, List runtimeFields) { List mappers = concreteFields.stream().map(MockFieldMapper::new).toList(); - RootObjectMapper.Builder builder = new RootObjectMapper.Builder("_doc"); + RootObjectMapper.Builder builder = new RootObjectMapper.Builder("_doc", ObjectMapper.Defaults.SUBOBJECTS); Map runtimeFieldTypes = runtimeFields.stream().collect(Collectors.toMap(RuntimeField::name, r -> r)); builder.addRuntimeFields(runtimeFieldTypes); Mapping mapping = new Mapping(builder.build(MapperBuilderContext.ROOT), new MetadataFieldMapper[0], Collections.emptyMap()); 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 5ee531ecdbd76..1e8b47a6027a1 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 @@ -35,6 +35,7 @@ import org.elasticsearch.index.mapper.MappingLookup; import org.elasticsearch.index.mapper.MappingParserContext; import org.elasticsearch.index.mapper.MetadataFieldMapper; +import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.mapper.RootObjectMapper; import org.elasticsearch.index.mapper.RoutingFieldMapper; import org.elasticsearch.index.shard.IndexEventListener; @@ -451,7 +452,7 @@ public static MetadataRolloverService getMetadataRolloverService( when(allocationService.reroute(any(ClusterState.class), any(String.class))).then(i -> i.getArguments()[0]); MappingLookup mappingLookup = null; if (dataStream != null) { - RootObjectMapper.Builder root = new RootObjectMapper.Builder("_doc"); + RootObjectMapper.Builder root = new RootObjectMapper.Builder("_doc", ObjectMapper.Defaults.SUBOBJECTS); root.add( new DateFieldMapper.Builder( dataStream.getTimeStampField().getName(), @@ -514,7 +515,9 @@ public static IndicesService mockIndicesServices(MappingLookup mappingLookup) th when(indexService.index()).thenReturn(indexMetadata.getIndex()); MapperService mapperService = mock(MapperService.class); - RootObjectMapper root = new RootObjectMapper.Builder(MapperService.SINGLE_MAPPING_NAME).build(MapperBuilderContext.ROOT); + RootObjectMapper root = new RootObjectMapper.Builder(MapperService.SINGLE_MAPPING_NAME, ObjectMapper.Defaults.SUBOBJECTS).build( + MapperBuilderContext.ROOT + ); Mapping mapping = new Mapping(root, new MetadataFieldMapper[0], null); DocumentMapper documentMapper = mock(DocumentMapper.class); when(documentMapper.mapping()).thenReturn(mapping);