diff --git a/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml b/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml index 39320d121366b..2c7937aeaccb7 100644 --- a/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml +++ b/rest-api-spec/src/main/resources/rest-api-spec/test/field_caps/10_basic.yml @@ -24,6 +24,16 @@ setup: nested2: type: float doc_values: false + level1: + type: nested + properties: + level2: + type: object + properties: + leaf1: + type: text + index: false + - do: indices.create: index: test2 @@ -48,6 +58,15 @@ setup: nested2: type: float doc_values: true + level1: + type: nested + properties: + level2: + type: object + properties: + leaf1: + type: text + index: false - do: indices.create: index: test3 @@ -64,7 +83,7 @@ setup: geo: type: keyword object: - type: object + type: nested properties: nested1 : type : long @@ -72,6 +91,15 @@ setup: nested2: type: keyword doc_values: false + level1: + type: object + properties: + level2: + type: object + properties: + leaf1: + type: text + index: false --- "Get simple field caps": @@ -112,7 +140,7 @@ setup: - is_false: fields.geo.keyword.non_searchable_indices - is_false: fields.geo.keyword.on_aggregatable_indices --- -"Get nested field caps": +"Get leaves field caps": - do: field_caps: @@ -140,6 +168,47 @@ setup: - is_false: fields.object\.nested2.keyword.non_aggregatable_indices - is_false: fields.object\.nested2.keyword.non_searchable_indices --- +"Get object and nested field caps": + - skip: + version: " - 6.99.99" + reason: object and nested fields are returned since 7.0 + + - do: + field_caps: + index: 'test1,test2,test3' + fields: object*,level1* + + - match: {fields.object.object.indices: ["test1", "test2"]} + - match: {fields.object.object.searchable: false} + - match: {fields.object.object.aggregatable: false} + - is_false: fields.object.object.non_aggregatable_indices + - is_false: fields.object.object.non_searchable_indices + - match: {fields.object.nested.indices: ["test3"]} + - match: {fields.object.nested.searchable: false} + - match: {fields.object.nested.aggregatable: false} + - is_false: fields.object.nested.non_aggregatable_indices + - is_false: fields.object.nested.non_searchable_indices + - match: {fields.level1.nested.indices: ["test1", "test2"]} + - match: {fields.level1.nested.searchable: false} + - match: {fields.level1.nested.aggregatable: false} + - is_false: fields.level1.nested.non_aggregatable_indices + - is_false: fields.level1.nested.non_searchable_indices + - match: {fields.level1.object.indices: ["test3"]} + - match: {fields.level1.object.searchable: false} + - match: {fields.level1.object.aggregatable: false} + - is_false: fields.level1.object.non_aggregatable_indices + - is_false: fields.level1.object.non_searchable_indices + - match: {fields.level1\.level2.object.searchable: false} + - match: {fields.level1\.level2.object.aggregatable: false} + - is_false: fields.level1\.level2.object.indices + - is_false: fields.level1\.level2.object.non_aggregatable_indices + - is_false: fields.level1\.level2.object.non_searchable_indices + - match: {fields.level1\.level2\.leaf1.text.searchable: false} + - match: {fields.level1\.level2\.leaf1.text.aggregatable: false} + - is_false: fields.level1\.level2\.leaf1.text.indices + - is_false: fields.level1\.level2\.leaf1.text.non_aggregatable_indices + - is_false: fields.level1\.level2\.leaf1.text..non_searchable_indices +--- "Get prefix field caps": - do: diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java index 18f33ab397f73..4ce810be9311d 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java @@ -31,6 +31,7 @@ import org.elasticsearch.common.settings.Settings; import org.elasticsearch.index.mapper.MappedFieldType; import org.elasticsearch.index.mapper.MapperService; +import org.elasticsearch.index.mapper.ObjectMapper; import org.elasticsearch.index.shard.ShardId; import org.elasticsearch.indices.IndicesService; import org.elasticsearch.threadpool.ThreadPool; @@ -86,6 +87,26 @@ protected FieldCapabilitiesIndexResponse shardOperation(final FieldCapabilitiesI if (indicesService.isMetaDataField(field) || fieldPredicate.test(ft.name())) { FieldCapabilities fieldCap = new FieldCapabilities(field, ft.typeName(), ft.isSearchable(), ft.isAggregatable()); responseMap.put(field, fieldCap); + } else { + continue; + } + // add nested and object fields + int dotIndex = ft.name().lastIndexOf('.'); + while (dotIndex > -1) { + String parentField = ft.name().substring(0, dotIndex); + if (responseMap.containsKey(parentField)) { + // we added this path on another field already + break; + } + // checks if the parent field contains sub-fields + if (mapperService.fullName(parentField) == null) { + // no field type, it must be an object field + ObjectMapper mapper = mapperService.getObjectMapper(parentField); + String type = mapper.nested().isNested() ? "nested" : "object"; + FieldCapabilities fieldCap = new FieldCapabilities(parentField, type, false, false); + responseMap.put(parentField, fieldCap); + } + dotIndex = parentField.lastIndexOf('.'); } } } diff --git a/server/src/test/java/org/elasticsearch/index/mapper/FieldFilterMapperPluginTests.java b/server/src/test/java/org/elasticsearch/index/mapper/FieldFilterMapperPluginTests.java index af29edcef30de..07a80a31debb6 100644 --- a/server/src/test/java/org/elasticsearch/index/mapper/FieldFilterMapperPluginTests.java +++ b/server/src/test/java/org/elasticsearch/index/mapper/FieldFilterMapperPluginTests.java @@ -35,9 +35,12 @@ import org.elasticsearch.test.ESSingleNodeTestCase; import org.junit.Before; +import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.function.Function; @@ -90,20 +93,26 @@ public void testGetFieldMappings() { } public void testFieldCapabilities() { + List allFields = new ArrayList<>(ALL_FLAT_FIELDS); + allFields.addAll(ALL_OBJECT_FIELDS); FieldCapabilitiesResponse index1 = client().fieldCaps(new FieldCapabilitiesRequest().fields("*").indices("index1")).actionGet(); - assertFieldCaps(index1, ALL_FLAT_FIELDS); + assertFieldCaps(index1, allFields); FieldCapabilitiesResponse filtered = client().fieldCaps(new FieldCapabilitiesRequest().fields("*").indices("filtered")).actionGet(); - assertFieldCaps(filtered, FILTERED_FLAT_FIELDS); + List filteredFields = new ArrayList<>(FILTERED_FLAT_FIELDS); + filteredFields.addAll(ALL_OBJECT_FIELDS); + assertFieldCaps(filtered, filteredFields); //double check that submitting the filtered mappings to an unfiltered index leads to the same field_caps output //as the one coming from a filtered index with same mappings GetMappingsResponse getMappingsResponse = client().admin().indices().prepareGetMappings("filtered").get(); ImmutableOpenMap filteredMapping = getMappingsResponse.getMappings().get("filtered"); assertAcked(client().admin().indices().prepareCreate("test").addMapping("_doc", filteredMapping.get("_doc").getSourceAsMap())); FieldCapabilitiesResponse test = client().fieldCaps(new FieldCapabilitiesRequest().fields("*").indices("test")).actionGet(); - assertFieldCaps(test, FILTERED_FLAT_FIELDS); + // properties.value is an object field in the new mapping + filteredFields.add("properties.value"); + assertFieldCaps(test, filteredFields); } - private static void assertFieldCaps(FieldCapabilitiesResponse fieldCapabilitiesResponse, String[] expectedFields) { + private static void assertFieldCaps(FieldCapabilitiesResponse fieldCapabilitiesResponse, Collection expectedFields) { Map> responseMap = fieldCapabilitiesResponse.get(); Set builtInMetaDataFields = IndicesModule.getBuiltInMetaDataFields(); for (String field : builtInMetaDataFields) { @@ -118,7 +127,7 @@ private static void assertFieldCaps(FieldCapabilitiesResponse fieldCapabilitiesR } private static void assertFieldMappings(Map> mappings, - String[] expectedFields) { + Collection expectedFields) { assertEquals(1, mappings.size()); Map fields = new HashMap<>(mappings.get("_doc")); Set builtInMetaDataFields = IndicesModule.getBuiltInMetaDataFields(); @@ -245,14 +254,18 @@ public Function> getFieldFilter() { } } - private static final String[] ALL_FLAT_FIELDS = new String[]{ + private static final Collection ALL_FLAT_FIELDS = Arrays.asList( "name.first", "name.last_visible", "birth", "age_visible", "address.street", "address.location", "address.area_visible", "properties.key_visible", "properties.key_visible.keyword", "properties.value", "properties.value.keyword_visible" - }; + ); - private static final String[] FILTERED_FLAT_FIELDS = new String[]{ - "name.last_visible", "age_visible", "address.area_visible", "properties.key_visible", "properties.value.keyword_visible" - }; + private static final Collection ALL_OBJECT_FIELDS = Arrays.asList( + "name", "address", "properties" + ); + + private static final Collection FILTERED_FLAT_FIELDS = Arrays.asList( + "name.last_visible", "age_visible", "address.area_visible", "properties.key_visible", "properties.value.keyword_visible" + ); private static final String TEST_ITEM = "{\n" + " \"_doc\": {\n" +