Skip to content

Commit fef0e1d

Browse files
authored
Create a class to hold field capabilities for one index. (#51721)
Currently, the same class `FieldCapabilities` is used both to represent the capabilities for one index, and also the merged capabilities across indices. To help clarify the logic, this PR proposes to create a separate class `IndexFieldCapabilities` for the capabilities in one index. The refactor will also help when adding `source_path` information in #49264, since the merged source path field will have a different structure from the field for a single index. Individual changes: * Add a new class IndexFieldCapabilities. * Remove extra constructor from FieldCapabilities. * Combine the add and merge methods in FieldCapabilities.Builder.
1 parent a0907c8 commit fef0e1d

File tree

11 files changed

+190
-70
lines changed

11 files changed

+190
-70
lines changed

client/rest-high-level/src/test/java/org/elasticsearch/client/SearchIT.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -1242,7 +1242,7 @@ public void testFieldCaps() throws IOException {
12421242
assertEquals(1, fieldResponse.size());
12431243

12441244
FieldCapabilities expectedTextCapabilities = new FieldCapabilities(
1245-
"field", "text", true, false, Collections.emptyMap());
1245+
"field", "text", true, false, null, null, null, Collections.emptyMap());
12461246
assertEquals(expectedTextCapabilities, fieldResponse.get("text"));
12471247
}
12481248

server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilities.java

+10-44
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,6 @@ public class FieldCapabilities implements Writeable, ToXContentObject {
5757
private static final ParseField NON_AGGREGATABLE_INDICES_FIELD = new ParseField("non_aggregatable_indices");
5858
private static final ParseField META_FIELD = new ParseField("meta");
5959

60-
private static Map<String, Set<String>> mapToMapOfSets(Map<String, String> map) {
61-
final Function<Map.Entry<String, String>, String> entryValueFunction = Map.Entry::getValue;
62-
return map.entrySet().stream().collect(
63-
Collectors.toUnmodifiableMap(Map.Entry::getKey, entryValueFunction.andThen(Set::of)));
64-
}
65-
6660
private final String name;
6761
private final String type;
6862
private final boolean isSearchable;
@@ -74,19 +68,6 @@ private static Map<String, Set<String>> mapToMapOfSets(Map<String, String> map)
7468

7569
private final Map<String, Set<String>> meta;
7670

77-
/**
78-
* Constructor for a single index.
79-
* @param name The name of the field.
80-
* @param type The type associated with the field.
81-
* @param isSearchable Whether this field is indexed for search.
82-
* @param isAggregatable Whether this field can be aggregated on.
83-
* @param meta Metadata about the field.
84-
*/
85-
public FieldCapabilities(String name, String type, boolean isSearchable, boolean isAggregatable,
86-
Map<String, String> meta) {
87-
this(name, type, isSearchable, isAggregatable, null, null, null, mapToMapOfSets(Objects.requireNonNull(meta)));
88-
}
89-
9071
/**
9172
* Constructor for a set of indices.
9273
* @param name The name of the field
@@ -102,11 +83,11 @@ public FieldCapabilities(String name, String type, boolean isSearchable, boolean
10283
* @param meta Merged metadata across indices.
10384
*/
10485
public FieldCapabilities(String name, String type,
105-
boolean isSearchable, boolean isAggregatable,
106-
String[] indices,
107-
String[] nonSearchableIndices,
108-
String[] nonAggregatableIndices,
109-
Map<String, Set<String>> meta) {
86+
boolean isSearchable, boolean isAggregatable,
87+
String[] indices,
88+
String[] nonSearchableIndices,
89+
String[] nonAggregatableIndices,
90+
Map<String, Set<String>> meta) {
11091
this.name = name;
11192
this.type = type;
11293
this.isSearchable = isSearchable;
@@ -117,7 +98,7 @@ public FieldCapabilities(String name, String type,
11798
this.meta = Objects.requireNonNull(meta);
11899
}
119100

120-
public FieldCapabilities(StreamInput in) throws IOException {
101+
FieldCapabilities(StreamInput in) throws IOException {
121102
this.name = in.readString();
122103
this.type = in.readString();
123104
this.isSearchable = in.readBoolean();
@@ -309,35 +290,20 @@ static class Builder {
309290
this.meta = new HashMap<>();
310291
}
311292

312-
private void add(String index, boolean search, boolean agg) {
293+
/**
294+
* Collect the field capabilities for an index.
295+
*/
296+
void add(String index, boolean search, boolean agg, Map<String, String> meta) {
313297
IndexCaps indexCaps = new IndexCaps(index, search, agg);
314298
indiceList.add(indexCaps);
315299
this.isSearchable &= search;
316300
this.isAggregatable &= agg;
317-
}
318-
319-
/**
320-
* Collect capabilities of an index.
321-
*/
322-
void add(String index, boolean search, boolean agg, Map<String, String> meta) {
323-
add(index, search, agg);
324301
for (Map.Entry<String, String> entry : meta.entrySet()) {
325302
this.meta.computeIfAbsent(entry.getKey(), key -> new HashSet<>())
326303
.add(entry.getValue());
327304
}
328305
}
329306

330-
/**
331-
* Merge another capabilities instance.
332-
*/
333-
void merge(String index, boolean search, boolean agg, Map<String, Set<String>> meta) {
334-
add(index, search, agg);
335-
for (Map.Entry<String, Set<String>> entry : meta.entrySet()) {
336-
this.meta.computeIfAbsent(entry.getKey(), key -> new HashSet<>())
337-
.addAll(entry.getValue());
338-
}
339-
}
340-
341307
List<String> getIndices() {
342308
return indiceList.stream().map(c -> c.name).collect(Collectors.toList());
343309
}

server/src/main/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesIndexResponse.java

+6-7
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,13 @@
2929
import java.util.Objects;
3030

3131
/**
32-
* Response for {@link FieldCapabilitiesIndexRequest} requests.
32+
* Response for {@link TransportFieldCapabilitiesIndexAction}.
3333
*/
3434
public class FieldCapabilitiesIndexResponse extends ActionResponse implements Writeable {
3535
private String indexName;
36-
private Map<String, FieldCapabilities> responseMap;
36+
private Map<String, IndexFieldCapabilities> responseMap;
3737

38-
FieldCapabilitiesIndexResponse(String indexName, Map<String, FieldCapabilities> responseMap) {
38+
FieldCapabilitiesIndexResponse(String indexName, Map<String, IndexFieldCapabilities> responseMap) {
3939
this.indexName = indexName;
4040
this.responseMap = responseMap;
4141
}
@@ -44,10 +44,9 @@ public class FieldCapabilitiesIndexResponse extends ActionResponse implements Wr
4444
super(in);
4545
this.indexName = in.readString();
4646
this.responseMap =
47-
in.readMap(StreamInput::readString, FieldCapabilities::new);
47+
in.readMap(StreamInput::readString, IndexFieldCapabilities::new);
4848
}
4949

50-
5150
/**
5251
* Get the index name
5352
*/
@@ -58,15 +57,15 @@ public String getIndexName() {
5857
/**
5958
* Get the field capabilities map
6059
*/
61-
public Map<String, FieldCapabilities> get() {
60+
public Map<String, IndexFieldCapabilities> get() {
6261
return responseMap;
6362
}
6463

6564
/**
6665
*
6766
* Get the field capabilities for the provided {@code field}
6867
*/
69-
public FieldCapabilities getField(String field) {
68+
public IndexFieldCapabilities getField(String field) {
7069
return responseMap.get(field);
7170
}
7271

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Licensed to Elasticsearch under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.elasticsearch.action.fieldcaps;
21+
22+
import org.elasticsearch.Version;
23+
import org.elasticsearch.common.io.stream.StreamInput;
24+
import org.elasticsearch.common.io.stream.StreamOutput;
25+
import org.elasticsearch.common.io.stream.Writeable;
26+
27+
import java.io.IOException;
28+
import java.util.Map;
29+
import java.util.Objects;
30+
import java.util.Set;
31+
import java.util.stream.Collectors;
32+
33+
/**
34+
* Describes the capabilities of a field in a single index.
35+
*/
36+
public class IndexFieldCapabilities implements Writeable {
37+
38+
private final String name;
39+
private final String type;
40+
private final boolean isSearchable;
41+
private final boolean isAggregatable;
42+
private final Map<String, String> meta;
43+
44+
/**
45+
* @param name The name of the field.
46+
* @param type The type associated with the field.
47+
* @param isSearchable Whether this field is indexed for search.
48+
* @param isAggregatable Whether this field can be aggregated on.
49+
* @param meta Metadata about the field.
50+
*/
51+
IndexFieldCapabilities(String name, String type,
52+
boolean isSearchable, boolean isAggregatable,
53+
Map<String, String> meta) {
54+
55+
this.name = name;
56+
this.type = type;
57+
this.isSearchable = isSearchable;
58+
this.isAggregatable = isAggregatable;
59+
this.meta = meta;
60+
}
61+
62+
IndexFieldCapabilities(StreamInput in) throws IOException {
63+
if (in.getVersion().onOrAfter(Version.V_8_0_0)) {
64+
this.name = in.readString();
65+
this.type = in.readString();
66+
this.isSearchable = in.readBoolean();
67+
this.isAggregatable = in.readBoolean();
68+
this.meta = in.readMap(StreamInput::readString, StreamInput::readString);
69+
} else {
70+
// Previously we reused the FieldCapabilities class to represent index field capabilities.
71+
FieldCapabilities fieldCaps = new FieldCapabilities(in);
72+
this.name = fieldCaps.getName();
73+
this.type = fieldCaps.getType();
74+
this.isSearchable = fieldCaps.isSearchable();
75+
this.isAggregatable = fieldCaps.isAggregatable();
76+
this.meta = fieldCaps.meta().entrySet().stream().collect(Collectors.toMap(
77+
Map.Entry::getKey,
78+
entry -> entry.getValue().iterator().next()));
79+
}
80+
}
81+
82+
@Override
83+
public void writeTo(StreamOutput out) throws IOException {
84+
if (out.getVersion().onOrAfter(Version.V_8_0_0)) {
85+
out.writeString(name);
86+
out.writeString(type);
87+
out.writeBoolean(isSearchable);
88+
out.writeBoolean(isAggregatable);
89+
out.writeMap(meta, StreamOutput::writeString, StreamOutput::writeString);
90+
} else {
91+
// Previously we reused the FieldCapabilities class to represent index field capabilities.
92+
Map<String, Set<String>> wrappedMeta = meta.entrySet().stream().collect(Collectors.toMap(
93+
Map.Entry::getKey,
94+
entry -> Set.of(entry.getValue())));
95+
FieldCapabilities fieldCaps = new FieldCapabilities(name, type, isSearchable, isAggregatable, null, null, null, wrappedMeta);
96+
fieldCaps.writeTo(out);
97+
}
98+
}
99+
100+
public String getName() {
101+
return name;
102+
}
103+
104+
public String getType() {
105+
return type;
106+
}
107+
108+
public boolean isAggregatable() {
109+
return isAggregatable;
110+
}
111+
112+
public boolean isSearchable() {
113+
return isSearchable;
114+
}
115+
116+
public Map<String, String> meta() {
117+
return meta;
118+
}
119+
120+
@Override
121+
public boolean equals(Object o) {
122+
if (this == o) return true;
123+
if (o == null || getClass() != o.getClass()) return false;
124+
IndexFieldCapabilities that = (IndexFieldCapabilities) o;
125+
return isSearchable == that.isSearchable &&
126+
isAggregatable == that.isAggregatable &&
127+
Objects.equals(name, that.name) &&
128+
Objects.equals(type, that.type) &&
129+
Objects.equals(meta, that.meta);
130+
}
131+
132+
@Override
133+
public int hashCode() {
134+
return Objects.hash(name, type, isSearchable, isAggregatable, meta);
135+
}
136+
}

server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesAction.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -182,14 +182,14 @@ private void addUnmappedFields(String[] indices, String field, Map<String, Field
182182
}
183183

184184
private void innerMerge(Map<String, Map<String, FieldCapabilities.Builder>> responseMapBuilder,
185-
String indexName, Map<String, FieldCapabilities> map) {
186-
for (Map.Entry<String, FieldCapabilities> entry : map.entrySet()) {
185+
String indexName, Map<String, IndexFieldCapabilities> map) {
186+
for (Map.Entry<String, IndexFieldCapabilities> entry : map.entrySet()) {
187187
final String field = entry.getKey();
188-
final FieldCapabilities fieldCap = entry.getValue();
188+
final IndexFieldCapabilities fieldCap = entry.getValue();
189189
Map<String, FieldCapabilities.Builder> typeMap = responseMapBuilder.computeIfAbsent(field, f -> new HashMap<>());
190190
FieldCapabilities.Builder builder = typeMap.computeIfAbsent(fieldCap.getType(),
191191
key -> new FieldCapabilities.Builder(field, key));
192-
builder.merge(indexName, fieldCap.isSearchable(), fieldCap.isAggregatable(), fieldCap.meta());
192+
builder.add(indexName, fieldCap.isSearchable(), fieldCap.isAggregatable(), fieldCap.meta());
193193
}
194194
}
195195
}

server/src/main/java/org/elasticsearch/action/fieldcaps/TransportFieldCapabilitiesIndexAction.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -84,14 +84,14 @@ protected FieldCapabilitiesIndexResponse shardOperation(final FieldCapabilitiesI
8484
fieldNames.addAll(mapperService.simpleMatchToFullName(field));
8585
}
8686
Predicate<String> fieldPredicate = indicesService.getFieldFilter().apply(shardId.getIndexName());
87-
Map<String, FieldCapabilities> responseMap = new HashMap<>();
87+
Map<String, IndexFieldCapabilities> responseMap = new HashMap<>();
8888
for (String field : fieldNames) {
8989
MappedFieldType ft = mapperService.fullName(field);
9090
if (ft != null) {
9191
if (indicesService.isMetaDataField(mapperService.getIndexSettings().getIndexVersionCreated(), field)
9292
|| fieldPredicate.test(ft.name())) {
93-
FieldCapabilities fieldCap = new FieldCapabilities(field, ft.typeName(), ft.isSearchable(), ft.isAggregatable(),
94-
ft.meta());
93+
IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(field, ft.typeName(),
94+
ft.isSearchable(), ft.isAggregatable(), ft.meta());
9595
responseMap.put(field, fieldCap);
9696
} else {
9797
continue;
@@ -109,7 +109,8 @@ protected FieldCapabilitiesIndexResponse shardOperation(final FieldCapabilitiesI
109109
// no field type, it must be an object field
110110
ObjectMapper mapper = mapperService.getObjectMapper(parentField);
111111
String type = mapper.nested().isNested() ? "nested" : "object";
112-
FieldCapabilities fieldCap = new FieldCapabilities(parentField, type, false, false, Collections.emptyMap());
112+
IndexFieldCapabilities fieldCap = new IndexFieldCapabilities(parentField, type,
113+
false, false, Collections.emptyMap());
113114
responseMap.put(parentField, fieldCap);
114115
}
115116
dotIndex = parentField.lastIndexOf('.');

server/src/test/java/org/elasticsearch/action/fieldcaps/FieldCapabilitiesResponseTests.java

+20-2
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,35 @@ protected Writeable.Reader<FieldCapabilitiesResponse> instanceReader() {
4949
}
5050

5151
private FieldCapabilitiesIndexResponse createRandomIndexResponse() {
52-
Map<String, FieldCapabilities> responses = new HashMap<>();
52+
Map<String, IndexFieldCapabilities> responses = new HashMap<>();
5353

5454
String[] fields = generateRandomStringArray(5, 10, false, true);
5555
assertNotNull(fields);
5656

5757
for (String field : fields) {
58-
responses.put(field, FieldCapabilitiesTests.randomFieldCaps(field));
58+
responses.put(field, randomFieldCaps(field));
5959
}
6060
return new FieldCapabilitiesIndexResponse(randomAsciiLettersOfLength(10), responses);
6161
}
6262

63+
private static IndexFieldCapabilities randomFieldCaps(String fieldName) {
64+
Map<String, String> meta;
65+
switch (randomInt(2)) {
66+
case 0:
67+
meta = Collections.emptyMap();
68+
break;
69+
case 1:
70+
meta = Map.of("key", "value");
71+
break;
72+
default:
73+
meta = Map.of("key1", "value1", "key2", "value2");
74+
break;
75+
}
76+
77+
return new IndexFieldCapabilities(fieldName, randomAlphaOfLengthBetween(5, 20),
78+
randomBoolean(), randomBoolean(), meta);
79+
}
80+
6381
@Override
6482
protected FieldCapabilitiesResponse mutateInstance(FieldCapabilitiesResponse response) {
6583
Map<String, Map<String, FieldCapabilities>> mutatedResponses = new HashMap<>(response.get());

server/src/test/java/org/elasticsearch/action/fieldcaps/MergedFieldCapabilitiesResponseTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -152,7 +152,7 @@ public void testEmptyResponse() throws IOException {
152152

153153
private static FieldCapabilitiesResponse createSimpleResponse() {
154154
Map<String, FieldCapabilities> titleCapabilities = new HashMap<>();
155-
titleCapabilities.put("text", new FieldCapabilities("title", "text", true, false, Collections.emptyMap()));
155+
titleCapabilities.put("text", new FieldCapabilities("title", "text", true, false, null, null, null, Collections.emptyMap()));
156156

157157
Map<String, FieldCapabilities> ratingCapabilities = new HashMap<>();
158158
ratingCapabilities.put("long", new FieldCapabilities("rating", "long",

0 commit comments

Comments
 (0)