Browse Source

Remove mapper service access in field capabilities (#65942)

The commit replaces the mapper service that is used to perform lookups in
the field capabilities action by a query shard context.
This change also ensures that we don't check for nested and object fields if the leaf is a runtime field.
Jim Ferenczi 4 years ago
parent
commit
d076ed29be

+ 59 - 42
server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java

@@ -43,10 +43,14 @@ import org.elasticsearch.common.Nullable;
 import org.elasticsearch.common.inject.Inject;
 import org.elasticsearch.common.io.stream.StreamInput;
 import org.elasticsearch.common.logging.LoggerMessageFormat;
+import org.elasticsearch.index.IndexService;
+import org.elasticsearch.index.engine.Engine;
 import org.elasticsearch.index.mapper.MappedFieldType;
-import org.elasticsearch.index.mapper.MapperService;
 import org.elasticsearch.index.mapper.ObjectMapper;
+import org.elasticsearch.index.mapper.RuntimeFieldType;
 import org.elasticsearch.index.query.MatchAllQueryBuilder;
+import org.elasticsearch.index.query.QueryShardContext;
+import org.elasticsearch.index.shard.IndexShard;
 import org.elasticsearch.index.shard.ShardId;
 import org.elasticsearch.indices.IndicesService;
 import org.elasticsearch.search.SearchService;
@@ -108,60 +112,73 @@ public class TransportFieldCapabilitiesIndexAction
     }
 
     private FieldCapabilitiesIndexResponse shardOperation(final FieldCapabilitiesIndexRequest request) throws IOException {
-        if (canMatchShard(request) == false) {
-            return new FieldCapabilitiesIndexResponse(request.index(), Collections.emptyMap(), false);
-        }
-        ShardId shardId = request.shardId();
-        MapperService mapperService = indicesService.indexServiceSafe(shardId.getIndex()).mapperService();
-        Set<String> fieldNames = new HashSet<>();
-        for (String field : request.fields()) {
-            fieldNames.addAll(mapperService.simpleMatchToFullName(field));
-        }
-        Predicate<String> fieldPredicate = indicesService.getFieldFilter().apply(shardId.getIndexName());
-        Map<String, IndexFieldCapabilities> responseMap = new HashMap<>();
-        for (String field : fieldNames) {
-            MappedFieldType ft = mapperService.fieldType(field);
-            if (ft != null) {
-                if (mapperService.isMetadataField(field)
-                    || fieldPredicate.test(ft.name())) {
-                    IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(field, ft.familyTypeName(),
-                        ft.isSearchable(), ft.isAggregatable(), ft.meta());
-                    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;
+        final ShardId shardId = request.shardId();
+        final IndexService indexService = indicesService.indexServiceSafe(shardId.getIndex());
+        final IndexShard indexShard = indexService.getShard(request.shardId().getId());
+        try (Engine.Searcher searcher = indexShard.acquireSearcher(Engine.CAN_MATCH_SEARCH_SOURCE)) {
+
+            final QueryShardContext queryShardContext = indexService.newQueryShardContext(shardId.id(), searcher,
+                request::nowInMillis, null, Collections.emptyMap());
+
+            if (canMatchShard(request, queryShardContext) == false) {
+                return new FieldCapabilitiesIndexResponse(request.index(), Collections.emptyMap(), false);
+            }
+
+            Set<String> fieldNames = new HashSet<>();
+            for (String field : request.fields()) {
+                fieldNames.addAll(queryShardContext.simpleMatchToIndexNames(field));
+            }
+
+            Predicate<String> fieldPredicate = indicesService.getFieldFilter().apply(shardId.getIndexName());
+            Map<String, IndexFieldCapabilities> responseMap = new HashMap<>();
+            for (String field : fieldNames) {
+                MappedFieldType ft = queryShardContext.getFieldType(field);
+                if (ft != null) {
+                    if (queryShardContext.isMetadataField(field)
+                            || fieldPredicate.test(ft.name())) {
+                        IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(field, ft.familyTypeName(),
+                            ft.isSearchable(), ft.isAggregatable(), ft.meta());
+                        responseMap.put(field, fieldCap);
+                    } else {
+                        continue;
                     }
-                    // checks if the parent field contains sub-fields
-                    if (mapperService.fieldType(parentField) == null) {
-                        // no field type, it must be an object field
-                        ObjectMapper mapper = mapperService.getObjectMapper(parentField);
-                        String type = mapper.nested().isNested() ? "nested" : "object";
-                        IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(parentField, type,
-                            false, false, Collections.emptyMap());
-                        responseMap.put(parentField, fieldCap);
+
+                    // Check the ancestor of the field to find nested and object fields.
+                    // Runtime fields are excluded since they can override any path.
+                    if (ft instanceof RuntimeFieldType == false) {
+                        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 (queryShardContext.getFieldType(parentField) == null) {
+                                // no field type, it must be an object field
+                                ObjectMapper mapper = queryShardContext.getObjectMapper(parentField);
+                                String type = mapper.nested().isNested() ? "nested" : "object";
+                                IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(parentField, type,
+                                    false, false, Collections.emptyMap());
+                                responseMap.put(parentField, fieldCap);
+                            }
+                            dotIndex = parentField.lastIndexOf('.');
+                        }
                     }
-                    dotIndex = parentField.lastIndexOf('.');
                 }
             }
+            return new FieldCapabilitiesIndexResponse(request.index(), responseMap, true);
         }
-        return new FieldCapabilitiesIndexResponse(request.index(), responseMap, true);
     }
 
-    private boolean canMatchShard(FieldCapabilitiesIndexRequest req) throws IOException {
+    private boolean canMatchShard(FieldCapabilitiesIndexRequest req, QueryShardContext queryShardContext) throws IOException {
         if (req.indexFilter() == null || req.indexFilter() instanceof MatchAllQueryBuilder) {
             return true;
         }
         assert req.nowInMillis() != 0L;
         ShardSearchRequest searchRequest = new ShardSearchRequest(req.shardId(), req.nowInMillis(), AliasFilter.EMPTY);
         searchRequest.source(new SearchSourceBuilder().query(req.indexFilter()));
-        return searchService.canMatch(searchRequest).canMatch();
+        return SearchService.queryStillMatchesAfterRewrite(searchRequest, queryShardContext);
     }
 
     private ClusterBlockException checkGlobalBlock(ClusterState state) {

+ 11 - 1
x-pack/plugin/src/test/resources/rest-api-spec/test/runtime_fields/80_multiple_indices.yml

@@ -56,6 +56,11 @@ setup:
                     }
                   params:
                     min_v: 5.0
+              message.keyword:
+                type: keyword
+                script:
+                  source: |
+                    emit("hello world")
 
             properties:
               timestamp:
@@ -134,9 +139,10 @@ setup:
   - do:
       field_caps:
         index: sensor*
-        fields: [day_of_week, voltage_*, tomorrow, ip, over_v]
+        fields: [day_of_week, voltage_*, tomorrow, ip, over_v, message.keyword]
 
   - match: {indices: [sensor1, sensor2]}
+  - length: {fields: 7}
   - match: {fields.day_of_week.keyword.searchable: true}
   - match: {fields.day_of_week.keyword.aggregatable: true}
   - is_false: fields.day_of_week.keyword.indices
@@ -155,6 +161,10 @@ setup:
   - match: {fields.over_v.boolean.searchable: true}
   - match: {fields.over_v.boolean.aggregatable: true}
   - is_false: fields.over_v.boolean.indices
+  - match: {fields.message\.keyword.keyword.searchable: true}
+  - match: {fields.message\.keyword.keyword.aggregatable: true}
+  - is_false: fields.message\.keyword.keyword.indices
+  - is_false: fields.message
 
 ---
 "terms agg - keyword":