diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java index c57a241474b06..af669ff580af1 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java @@ -1255,7 +1255,7 @@ public void testFieldCaps() throws IOException { assertEquals(1, fieldResponse.size()); FieldCapabilities expectedTextCapabilities = new FieldCapabilities( - "field", "text", true, false, Collections.emptyMap()); + "field", "text", true, false, null, null, null, Collections.emptyMap()); assertEquals(expectedTextCapabilities, fieldResponse.get("text")); } diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilities.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilities.java index 263452bbfd955..04d649bc5ba74 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilities.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilities.java @@ -57,13 +57,6 @@ public class FieldCapabilities implements Writeable, ToXContentObject { private static final ParseField NON_AGGREGATABLE_INDICES_FIELD = new ParseField("non_aggregatable_indices"); private static final ParseField META_FIELD = new ParseField("meta"); - private static Map> mapToMapOfSets(Map map) { - final Function, String> entryValueFunction = Map.Entry::getValue; - return Collections.unmodifiableMap(map.entrySet().stream().collect( - Collectors.toMap(Map.Entry::getKey, entryValueFunction.andThen(Arrays::asList).andThen(HashSet::new) - .andThen(Collections::unmodifiableSet)))); - } - private final String name; private final String type; private final boolean isSearchable; @@ -75,19 +68,6 @@ private static Map> mapToMapOfSets(Map map) private final Map> meta; - /** - * Constructor for a single index. - * @param name The name of the field. - * @param type The type associated with the field. - * @param isSearchable Whether this field is indexed for search. - * @param isAggregatable Whether this field can be aggregated on. - * @param meta Metadata about the field. - */ - public FieldCapabilities(String name, String type, boolean isSearchable, boolean isAggregatable, - Map meta) { - this(name, type, isSearchable, isAggregatable, null, null, null, mapToMapOfSets(Objects.requireNonNull(meta))); - } - /** * Constructor for a set of indices. * @param name The name of the field @@ -103,11 +83,11 @@ public FieldCapabilities(String name, String type, boolean isSearchable, boolean * @param meta Merged metadata across indices. */ public FieldCapabilities(String name, String type, - boolean isSearchable, boolean isAggregatable, - String[] indices, - String[] nonSearchableIndices, - String[] nonAggregatableIndices, - Map> meta) { + boolean isSearchable, boolean isAggregatable, + String[] indices, + String[] nonSearchableIndices, + String[] nonAggregatableIndices, + Map> meta) { this.name = name; this.type = type; this.isSearchable = isSearchable; @@ -118,7 +98,7 @@ public FieldCapabilities(String name, String type, this.meta = Objects.requireNonNull(meta); } - public FieldCapabilities(StreamInput in) throws IOException { + FieldCapabilities(StreamInput in) throws IOException { this.name = in.readString(); this.type = in.readString(); this.isSearchable = in.readBoolean(); @@ -310,35 +290,20 @@ static class Builder { this.meta = new HashMap<>(); } - private void add(String index, boolean search, boolean agg) { + /** + * Collect the field capabilities for an index. + */ + void add(String index, boolean search, boolean agg, Map meta) { IndexCaps indexCaps = new IndexCaps(index, search, agg); indiceList.add(indexCaps); this.isSearchable &= search; this.isAggregatable &= agg; - } - - /** - * Collect capabilities of an index. - */ - void add(String index, boolean search, boolean agg, Map meta) { - add(index, search, agg); for (Map.Entry entry : meta.entrySet()) { this.meta.computeIfAbsent(entry.getKey(), key -> new HashSet<>()) .add(entry.getValue()); } } - /** - * Merge another capabilities instance. - */ - void merge(String index, boolean search, boolean agg, Map> meta) { - add(index, search, agg); - for (Map.Entry> entry : meta.entrySet()) { - this.meta.computeIfAbsent(entry.getKey(), key -> new HashSet<>()) - .addAll(entry.getValue()); - } - } - List getIndices() { return indiceList.stream().map(c -> c.name).collect(Collectors.toList()); } diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java index cc5c0921c6640..2590f0ab44c2e 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java @@ -29,13 +29,13 @@ import java.util.Objects; /** - * Response for {@link FieldCapabilitiesIndexRequest} requests. + * Response for {@link TransportFieldCapabilitiesIndexAction}. */ public class FieldCapabilitiesIndexResponse extends ActionResponse implements Writeable { private String indexName; - private Map responseMap; + private Map responseMap; - FieldCapabilitiesIndexResponse(String indexName, Map responseMap) { + FieldCapabilitiesIndexResponse(String indexName, Map responseMap) { this.indexName = indexName; this.responseMap = responseMap; } @@ -44,10 +44,9 @@ public class FieldCapabilitiesIndexResponse extends ActionResponse implements Wr super(in); this.indexName = in.readString(); this.responseMap = - in.readMap(StreamInput::readString, FieldCapabilities::new); + in.readMap(StreamInput::readString, IndexFieldCapabilities::new); } - /** * Get the index name */ @@ -58,7 +57,7 @@ public String getIndexName() { /** * Get the field capabilities map */ - public Map get() { + public Map get() { return responseMap; } @@ -66,7 +65,7 @@ public Map get() { * * Get the field capabilities for the provided {@code field} */ - public FieldCapabilities getField(String field) { + public IndexFieldCapabilities getField(String field) { return responseMap.get(field); } diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/IndexFieldCapabilities.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/IndexFieldCapabilities.java new file mode 100644 index 0000000000000..58e8a75c641b0 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/IndexFieldCapabilities.java @@ -0,0 +1,136 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.action.fieldcaps; + +import org.elasticsearch.Version; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; + +import java.io.IOException; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Describes the capabilities of a field in a single index. + */ +public class IndexFieldCapabilities implements Writeable { + + private final String name; + private final String type; + private final boolean isSearchable; + private final boolean isAggregatable; + private final Map meta; + + /** + * @param name The name of the field. + * @param type The type associated with the field. + * @param isSearchable Whether this field is indexed for search. + * @param isAggregatable Whether this field can be aggregated on. + * @param meta Metadata about the field. + */ + IndexFieldCapabilities(String name, String type, + boolean isSearchable, boolean isAggregatable, + Map meta) { + + this.name = name; + this.type = type; + this.isSearchable = isSearchable; + this.isAggregatable = isAggregatable; + this.meta = meta; + } + + IndexFieldCapabilities(StreamInput in) throws IOException { + if (in.getVersion().onOrAfter(Version.V_8_0_0)) { + this.name = in.readString(); + this.type = in.readString(); + this.isSearchable = in.readBoolean(); + this.isAggregatable = in.readBoolean(); + this.meta = in.readMap(StreamInput::readString, StreamInput::readString); + } else { + // Previously we reused the FieldCapabilities class to represent index field capabilities. + FieldCapabilities fieldCaps = new FieldCapabilities(in); + this.name = fieldCaps.getName(); + this.type = fieldCaps.getType(); + this.isSearchable = fieldCaps.isSearchable(); + this.isAggregatable = fieldCaps.isAggregatable(); + this.meta = fieldCaps.meta().entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().iterator().next())); + } + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + if (out.getVersion().onOrAfter(Version.V_8_0_0)) { + out.writeString(name); + out.writeString(type); + out.writeBoolean(isSearchable); + out.writeBoolean(isAggregatable); + out.writeMap(meta, StreamOutput::writeString, StreamOutput::writeString); + } else { + // Previously we reused the FieldCapabilities class to represent index field capabilities. + Map> wrappedMeta = meta.entrySet().stream().collect(Collectors.toMap( + Map.Entry::getKey, + entry -> Set.of(entry.getValue()))); + FieldCapabilities fieldCaps = new FieldCapabilities(name, type, isSearchable, isAggregatable, null, null, null, wrappedMeta); + fieldCaps.writeTo(out); + } + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public boolean isAggregatable() { + return isAggregatable; + } + + public boolean isSearchable() { + return isSearchable; + } + + public Map meta() { + return meta; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + IndexFieldCapabilities that = (IndexFieldCapabilities) o; + return isSearchable == that.isSearchable && + isAggregatable == that.isAggregatable && + Objects.equals(name, that.name) && + Objects.equals(type, that.type) && + Objects.equals(meta, that.meta); + } + + @Override + public int hashCode() { + return Objects.hash(name, type, isSearchable, isAggregatable, meta); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java b/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java index d01a1807eca46..98b7510f149d6 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java @@ -181,14 +181,14 @@ private void addUnmappedFields(String[] indices, String field, Map> responseMapBuilder, - String indexName, Map map) { - for (Map.Entry entry : map.entrySet()) { + String indexName, Map map) { + for (Map.Entry entry : map.entrySet()) { final String field = entry.getKey(); - final FieldCapabilities fieldCap = entry.getValue(); + final IndexFieldCapabilities fieldCap = entry.getValue(); Map typeMap = responseMapBuilder.computeIfAbsent(field, f -> new HashMap<>()); FieldCapabilities.Builder builder = typeMap.computeIfAbsent(fieldCap.getType(), key -> new FieldCapabilities.Builder(field, key)); - builder.merge(indexName, fieldCap.isSearchable(), fieldCap.isAggregatable(), fieldCap.meta()); + builder.add(indexName, fieldCap.isSearchable(), fieldCap.isAggregatable(), fieldCap.meta()); } } } 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 445e24e460b8c..c2e971ec39ef7 100644 --- a/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java +++ b/server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java @@ -81,14 +81,14 @@ protected FieldCapabilitiesIndexResponse shardOperation(final FieldCapabilitiesI fieldNames.addAll(mapperService.simpleMatchToFullName(field)); } Predicate fieldPredicate = indicesService.getFieldFilter().apply(shardId.getIndexName()); - Map responseMap = new HashMap<>(); + Map responseMap = new HashMap<>(); for (String field : fieldNames) { MappedFieldType ft = mapperService.fullName(field); if (ft != null) { if (indicesService.isMetaDataField(mapperService.getIndexSettings().getIndexVersionCreated(), field) || fieldPredicate.test(ft.name())) { - FieldCapabilities fieldCap = new FieldCapabilities(field, ft.typeName(), ft.isSearchable(), ft.isAggregatable(), - ft.meta()); + IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(field, ft.typeName(), + ft.isSearchable(), ft.isAggregatable(), ft.meta()); responseMap.put(field, fieldCap); } else { continue; @@ -106,7 +106,8 @@ protected FieldCapabilitiesIndexResponse shardOperation(final FieldCapabilitiesI // 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, Collections.emptyMap()); + IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(parentField, type, + false, false, Collections.emptyMap()); responseMap.put(parentField, fieldCap); } dotIndex = parentField.lastIndexOf('.'); diff --git a/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java b/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java index 0c329bee8191a..18f4f2a85d5e4 100644 --- a/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java @@ -49,17 +49,35 @@ protected Writeable.Reader instanceReader() { } private FieldCapabilitiesIndexResponse createRandomIndexResponse() { - Map responses = new HashMap<>(); + Map responses = new HashMap<>(); String[] fields = generateRandomStringArray(5, 10, false, true); assertNotNull(fields); for (String field : fields) { - responses.put(field, FieldCapabilitiesTests.randomFieldCaps(field)); + responses.put(field, randomFieldCaps(field)); } return new FieldCapabilitiesIndexResponse(randomAsciiLettersOfLength(10), responses); } + private static IndexFieldCapabilities randomFieldCaps(String fieldName) { + Map meta; + switch (randomInt(2)) { + case 0: + meta = Collections.emptyMap(); + break; + case 1: + meta = Map.of("key", "value"); + break; + default: + meta = Map.of("key1", "value1", "key2", "value2"); + break; + } + + return new IndexFieldCapabilities(fieldName, randomAlphaOfLengthBetween(5, 20), + randomBoolean(), randomBoolean(), meta); + } + @Override protected FieldCapabilitiesResponse mutateInstance(FieldCapabilitiesResponse response) { Map> mutatedResponses = new HashMap<>(response.get()); diff --git a/server/src/test/java/org/elasticsearch/action/fieldcaps/MergedFieldCapabilitiesResponseTests.java b/server/src/test/java/org/elasticsearch/action/fieldcaps/MergedFieldCapabilitiesResponseTests.java index 13a634177650c..b98e9445fb9bb 100644 --- a/server/src/test/java/org/elasticsearch/action/fieldcaps/MergedFieldCapabilitiesResponseTests.java +++ b/server/src/test/java/org/elasticsearch/action/fieldcaps/MergedFieldCapabilitiesResponseTests.java @@ -152,7 +152,7 @@ public void testEmptyResponse() throws IOException { private static FieldCapabilitiesResponse createSimpleResponse() { Map titleCapabilities = new HashMap<>(); - titleCapabilities.put("text", new FieldCapabilities("title", "text", true, false, Collections.emptyMap())); + titleCapabilities.put("text", new FieldCapabilities("title", "text", true, false, null, null, null, Collections.emptyMap())); Map ratingCapabilities = new HashMap<>(); ratingCapabilities.put("long", new FieldCapabilities("rating", "long", diff --git a/server/src/test/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java b/server/src/test/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java index 254447abcf8eb..90ebd52492f57 100644 --- a/server/src/test/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java +++ b/server/src/test/java/org/elasticsearch/search/fieldcaps/FieldCapabilitiesIT.java @@ -135,7 +135,7 @@ public void testFieldAlias() { assertTrue(routeLength.containsKey("double")); assertEquals( - new FieldCapabilities("route_length_miles", "double", true, true, Collections.emptyMap()), + new FieldCapabilities("route_length_miles", "double", true, true, null, null, null, Collections.emptyMap()), routeLength.get("double")); } @@ -191,7 +191,7 @@ public void testWithUnmapped() { assertTrue(newField.containsKey("long")); assertEquals( - new FieldCapabilities("new_field", "long", true, true, Collections.emptyMap()), + new FieldCapabilities("new_field", "long", true, true, null, null, null, Collections.emptyMap()), newField.get("long")); } diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/dataframe/extractor/ExtractedFieldsDetectorTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/dataframe/extractor/ExtractedFieldsDetectorTests.java index dff5d8cd4ef2c..49a302a498b82 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/dataframe/extractor/ExtractedFieldsDetectorTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/dataframe/extractor/ExtractedFieldsDetectorTests.java @@ -968,7 +968,7 @@ private MockFieldCapsResponseBuilder addNonAggregatableField(String field, Strin private MockFieldCapsResponseBuilder addField(String field, boolean isAggregatable, String... types) { Map caps = new HashMap<>(); for (String type : types) { - caps.put(type, new FieldCapabilities(field, type, true, isAggregatable, Collections.emptyMap())); + caps.put(type, new FieldCapabilities(field, type, true, isAggregatable, null, null, null, Collections.emptyMap())); } fieldCaps.put(field, caps); return this; diff --git a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/index/IndexResolverTests.java b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/index/IndexResolverTests.java index f29f373272a1e..b53d1cbdf8133 100644 --- a/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/index/IndexResolverTests.java +++ b/x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/analysis/index/IndexResolverTests.java @@ -317,7 +317,7 @@ public void testMultipleCompatibleIndicesWithDifferentFields() { public void testIndexWithNoMapping() { Map> versionFC = singletonMap("_version", singletonMap("_index", new FieldCapabilities("_version", "_version", false, false, - Collections.emptyMap()))); + null, null, null, Collections.emptyMap()))); assertTrue(mergedMappings("*", new String[] { "empty" }, versionFC).isValid()); } @@ -392,7 +392,7 @@ private static class UpdateableFieldCapabilities extends FieldCapabilities { List nonAggregatableIndices = new ArrayList<>(); UpdateableFieldCapabilities(String name, String type, boolean isSearchable, boolean isAggregatable) { - super(name, type, isSearchable, isAggregatable, Collections.emptyMap()); + super(name, type, isSearchable, isAggregatable, null, null, null, Collections.emptyMap()); } @Override @@ -426,7 +426,7 @@ private static void assertEqualsMaps(Map left, Map right) { private void addFieldCaps(Map> fieldCaps, String name, String type, boolean isSearchable, boolean isAggregatable) { Map cap = new HashMap<>(); - cap.put(type, new FieldCapabilities(name, type, isSearchable, isAggregatable, Collections.emptyMap())); + cap.put(type, new FieldCapabilities(name, type, isSearchable, isAggregatable, null, null, null, Collections.emptyMap())); fieldCaps.put(name, cap); } @@ -439,4 +439,4 @@ private static List separateMappings(String indexPattern, String javaRe Map> fieldCaps) { return IndexResolver.separateMappings(SqlDataTypeRegistry.INSTANCE, indexPattern, javaRegex, indexNames, fieldCaps); } -} \ No newline at end of file +}